cz.maresmar.sfm.service.plugin.PluginUtils.java Source code

Java tutorial

Introduction

Here is the source code for cz.maresmar.sfm.service.plugin.PluginUtils.java

Source

/*
 * SmartFoodMenu - Android application for canteens extendable with plugins
 *
 * Copyright  2016-2018  Martin Mare <mmrmartin[at]gmail[dot]com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package cz.maresmar.sfm.service.plugin;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.JobIntentService;

import java.util.HashMap;

import cz.maresmar.sfm.plugin.ActionContract;
import cz.maresmar.sfm.provider.ProviderContract;
import cz.maresmar.sfm.provider.PublicProviderContract;
import timber.log.Timber;

/**
 * Helping class that allows start of plugins on different API levels
 *
 * @see JobIntentService
 * @see <a href="https://developer.android.com/about/versions/oreo/background">Background Execution Limits</a>
 */
public class PluginUtils {

    // To prevent someone from accidentally instantiating the utils class,
    // make the constructor private.
    private PluginUtils() {
    }

    /**
     * The same {@link JobIntentService} has to be started with same ID. So it's stored here...
     */
    private static HashMap<String, Integer> mPluginsIds = new HashMap<>();

    /**
     * Starts plugin according to API level
     *
     * @param context Some valid context
     * @param intent  Intent of plugin to be started
     * @see JobIntentService#enqueueWork(Context, ComponentName, int, Intent)
     */
    public static void startPlugin(@NonNull Context context, @NonNull Intent intent) {
        // Test if the plugin exists
        PackageManager manager = context.getPackageManager();
        if (manager.queryIntentServices(intent, 0).size() != 1) {
            Timber.e("Plugin %s not found", intent.getComponent());
            throw new IllegalArgumentException("Plugin not found " + intent.getComponent());
        }

        // Finds jobId for selected plugin
        String pluginName = ProviderContract.buildString(intent.getComponent());
        int jobId;
        if (!mPluginsIds.containsKey(pluginName)) {
            jobId = mPluginsIds.size();
            mPluginsIds.put(pluginName, jobId);
        } else {
            jobId = mPluginsIds.get(pluginName);
        }

        // Starts plugin according to API level
        if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
                || (context.getPackageName().equals(intent.getComponent().getPackageName()))) {
            JobIntentService.enqueueWork(context, intent.getComponent(), jobId, intent);
        } else {
            // On Android >= O with external plugin use BroadcastContract.BROADCAST_PLAN_RUN to
            // start plugin, because planning of external APK's service is not allowed

            Intent plan = new Intent();
            // Explicitly select a package to communicate with
            plan.setPackage(intent.getComponent().getPackageName());
            plan.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);

            plan.setAction(ActionContract.BROADCAST_PLAN_RUN);
            plan.putExtra(ActionContract.EXTRA_JOB_ID, jobId);
            plan.putExtra(ActionContract.EXTRA_INTENT_TO_DO, intent);
            context.sendBroadcast(plan);
        }
    }

    /**
     * Prepares plugin intent from plugin name
     * @param plugin Name of plugin
     * @return Intent that can start plugin
     * @see #startPlugin(Context, Intent)
     */
    @NonNull
    public static Intent buildPluginIntent(@NonNull String plugin) {
        // Creates the intent
        Intent intent = new Intent();

        ComponentName componentName = PublicProviderContract.parsePluginText(plugin);
        // Test result validity
        if (componentName == null) {
            Timber.e("Plugin name \"%s\" is badly formatted", plugin);
            throw new IllegalArgumentException("Plugin name is badly formatted " + plugin);
        }
        // Explicitly select service to communicate with
        intent.setComponent(componentName);
        return intent;
    }
}