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:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 */);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user