Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f4e019aa authored by Jason Monk's avatar Jason Monk
Browse files

Plugin fragment support

Allows fragments to be easily switched over to plugins and a provides
a convenient base class for plugins to use that makes sure the layout
inflater and context point at the plugin's and not sysui's.

Bug: 32609190
Test: runtest systemui

Change-Id: I6503947e980f66ddcd826f6ca9a92b591ce0eb1e
parent 0ceef211
Loading
Loading
Loading
Loading
+77 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package com.android.systemui.plugins;

import android.annotation.Nullable;
import android.app.Fragment;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.view.LayoutInflater;

public abstract class PluginFragment extends Fragment implements Plugin {

    private static final String KEY_PLUGIN_PACKAGE = "plugin_package_name";
    private Context mPluginContext;

    @Override
    public void onCreate(Context sysuiContext, Context pluginContext) {
        mPluginContext = pluginContext;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            Context sysuiContext = getContext();
            Context pluginContext = recreatePluginContext(sysuiContext, savedInstanceState);
            onCreate(sysuiContext, pluginContext);
        }
        if (mPluginContext == null) {
            throw new RuntimeException("PluginFragments must call super.onCreate("
                    + "Context sysuiContext, Context pluginContext)");
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(KEY_PLUGIN_PACKAGE, getContext().getPackageName());
    }

    private Context recreatePluginContext(Context sysuiContext, Bundle savedInstanceState) {
        final String pkg = savedInstanceState.getString(KEY_PLUGIN_PACKAGE);
        try {
            ApplicationInfo appInfo = sysuiContext.getPackageManager().getApplicationInfo(pkg, 0);
            return PluginManager.getInstance(sysuiContext).getContext(appInfo, pkg);
        } catch (NameNotFoundException e) {
            throw new RuntimeException("Plugin with invalid package? " + pkg, e);
        }
    }

    @Override
    public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
        return super.getLayoutInflater(savedInstanceState).cloneInContext(mPluginContext);
    }

    /**
     * Should only be called after {@link Plugin#onCreate(Context, Context)}.
     */
    @Override
    public Context getContext() {
        return mPluginContext != null ? mPluginContext : super.getContext();
    }
}
+10 −2
Original line number Diff line number Diff line
@@ -158,7 +158,11 @@ public class PluginInstanceManager<T extends Plugin> {
                case PLUGIN_DISCONNECTED:
                    if (DEBUG) Log.d(TAG, "onPluginDisconnected");
                    mListener.onPluginDisconnected((T) msg.obj);
                    if (!(msg.obj instanceof PluginFragment)) {
                        // Only call onDestroy for plugins that aren't fragments, as fragments
                        // will get the onDestroy as part of the fragment lifecycle.
                        ((T) msg.obj).onDestroy();
                    }
                    break;
                default:
                    super.handleMessage(msg);
@@ -186,8 +190,12 @@ public class PluginInstanceManager<T extends Plugin> {
                    for (int i = mPlugins.size() - 1; i >= 0; i--) {
                        PluginInfo<T> plugin = mPlugins.get(i);
                        mListener.onPluginDisconnected(plugin.mPlugin);
                        if (!(plugin.mPlugin instanceof PluginFragment)) {
                            // Only call onDestroy for plugins that aren't fragments, as fragments
                            // will get the onDestroy as part of the fragment lifecycle.
                            plugin.mPlugin.onDestroy();
                        }
                    }
                    mPlugins.clear();
                    handleQueryPlugins(null);
                    break;
+35 −1
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Build;
import android.os.HandlerThread;
@@ -26,6 +28,7 @@ import android.os.SystemProperties;
import android.util.ArrayMap;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper;

import dalvik.system.PathClassLoader;

@@ -163,6 +166,16 @@ public class PluginManager extends BroadcastReceiver {
        return mParentClassLoader;
    }

    public Context getAllPluginContext(Context context) {
        return new PluginContextWrapper(context,
                new AllPluginClassLoader(context.getClassLoader()));
    }

    public Context getContext(ApplicationInfo info, String pkg) throws NameNotFoundException {
        ClassLoader classLoader = getClassLoader(info.sourceDir, pkg);
        return new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader);
    }

    public static PluginManager getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new PluginManager(context.getApplicationContext());
@@ -170,6 +183,28 @@ public class PluginManager extends BroadcastReceiver {
        return sInstance;
    }

    private class AllPluginClassLoader extends ClassLoader {
        public AllPluginClassLoader(ClassLoader classLoader) {
            super(classLoader);
        }

        @Override
        public Class<?> loadClass(String s) throws ClassNotFoundException {
            try {
                return super.loadClass(s);
            } catch (ClassNotFoundException e) {
                for (ClassLoader classLoader : mClassLoaders.values()) {
                    try {
                        return classLoader.loadClass(s);
                    } catch (ClassNotFoundException e1) {
                        // Will re-throw e if all fail.
                    }
                }
                throw e;
            }
        }
    }

    @VisibleForTesting
    public static class PluginInstanceManagerFactory {
        public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
@@ -180,7 +215,6 @@ public class PluginManager extends BroadcastReceiver {
        }
    }


    // This allows plugins to include any libraries or copied code they want by only including
    // classes from the plugin library.
    private static class ClassLoaderFilter extends ClassLoader {
+2 −1
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.view.View;

import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.plugins.PluginManager;

import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -51,7 +52,7 @@ public class FragmentHostManager {
    private FragmentLifecycleCallbacks mLifecycleCallbacks;

    FragmentHostManager(Context context, FragmentService manager, View rootView) {
        mContext = context;
        mContext = PluginManager.getInstance(context).getAllPluginContext(context);
        mManager = manager;
        mRootView = rootView;
        mConfigChanges.applyNewConfig(context.getResources());
+86 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package com.android.systemui.fragments;

import android.app.Fragment;
import android.util.Log;
import android.view.View;

import com.android.systemui.plugins.FragmentBase;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;

public class PluginFragmentListener implements PluginListener<Plugin> {

    private static final String TAG = "PluginFragmentListener";

    private final FragmentHostManager mFragmentHostManager;
    private final PluginManager mPluginManager;
    private final Class<? extends Fragment> mDefaultClass;
    private final int mId;
    private final String mTag;
    private final Class<? extends FragmentBase> mExpectedInterface;

    public PluginFragmentListener(View view, String tag, int id,
            Class<? extends Fragment> defaultFragment,
            Class<? extends FragmentBase> expectedInterface) {
        mFragmentHostManager = FragmentHostManager.get(view);
        mPluginManager = PluginManager.getInstance(view.getContext());
        mExpectedInterface = expectedInterface;
        mTag = tag;
        mDefaultClass = defaultFragment;
        mId = id;
    }

    public void startListening(String action, int version) {
        try {
            setFragment(mDefaultClass.newInstance());
        } catch (InstantiationException | IllegalAccessException e) {
            Log.e(TAG, "Couldn't instantiate " + mDefaultClass.getName(), e);
        }
        mPluginManager.addPluginListener(action, this, version, false /* Only allow one */);
    }

    public void stopListening() {
        mPluginManager.removePluginListener(this);
    }

    private void setFragment(Fragment fragment) {
        mFragmentHostManager.getFragmentManager().beginTransaction()
                .replace(mId, fragment, mTag)
                .commit();
    }

    @Override
    public void onPluginConnected(Plugin plugin) {
        try {
            mExpectedInterface.cast(plugin);
            setFragment((Fragment) plugin);
        } catch (ClassCastException e) {
            Log.e(TAG, plugin.getClass().getName() + " must be a Fragment and implement "
                    + mExpectedInterface.getName(), e);
        }
    }

    @Override
    public void onPluginDisconnected(Plugin plugin) {
        try {
            setFragment(mDefaultClass.newInstance());
        } catch (InstantiationException | IllegalAccessException e) {
            Log.e(TAG, "Couldn't instantiate " + mDefaultClass.getName(), e);
        }
    }
}
Loading