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
This commit is contained in:
Jason Monk
2016-11-03 09:07:20 -04:00
parent 0ceef21187
commit f4e019aaad
7 changed files with 215 additions and 12 deletions

View File

@@ -0,0 +1,77 @@
/*
* 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();
}
}

View File

@@ -158,7 +158,11 @@ public class PluginInstanceManager<T extends Plugin> {
case PLUGIN_DISCONNECTED:
if (DEBUG) Log.d(TAG, "onPluginDisconnected");
mListener.onPluginDisconnected((T) msg.obj);
((T) msg.obj).onDestroy();
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,7 +190,11 @@ 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);
plugin.mPlugin.onDestroy();
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);

View File

@@ -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 {

View File

@@ -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());

View File

@@ -0,0 +1,86 @@
/*
* 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);
}
}
}

View File

@@ -1621,6 +1621,7 @@ public class NotificationPanelView extends PanelView implements
}
// Since there are QS tiles in the header now, we need to make sure we start listening
// immediately so they can be up to date.
if (mQs == null) return;
mQs.setHeaderListening(true);
}
@@ -1749,7 +1750,7 @@ public class NotificationPanelView extends PanelView implements
}
public void onQsHeightChanged() {
mQsMaxExpansionHeight = mQs.getDesiredHeight();
mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
if (mQsExpanded && mQsFullyExpanded) {
mQsExpansionHeight = mQsMaxExpansionHeight;
requestScrollerTopPaddingUpdate(false /* animate */);

View File

@@ -90,7 +90,6 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
@@ -126,8 +125,6 @@ import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.AutoReinflateContainer;
import com.android.systemui.AutoReinflateContainer.InflateListener;
import com.android.systemui.BatteryMeterView;
import com.android.systemui.DemoMode;
import com.android.systemui.EventLogConstants;
@@ -142,11 +139,11 @@ import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.PluginFragmentListener;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QS.ActivityStarter;
import com.android.systemui.plugins.qs.QS.BaseStatusBarHeader;
import com.android.systemui.qs.QSContainerImpl;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.recents.ScreenPinningRequest;
@@ -928,9 +925,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
View container = mStatusBarWindow.findViewById(R.id.qs_frame);
if (container != null) {
FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
fragmentHostManager.getFragmentManager().beginTransaction()
.replace(R.id.qs_frame, new QSFragment(), QS.TAG)
.commit();
new PluginFragmentListener(container, QS.TAG, R.id.qs_frame, QSFragment.class, QS.class)
.startListening(QS.ACTION, QS.VERSION);
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
mBluetoothController, mLocationController, mRotationLockController,
mNetworkController, mZenModeController, mHotspotController,