SysUI fragments: Integrate new support for constructing
Use a new system for constructing fragments so they can be swapped
out in place maintaining state. This will allow easier integration
with plugin lifecycle as parents who have child plugin fragments
can depend on the class existing and won't have to listen to
the lifecycle.
Test: runtest systemui
Change-Id: I517f4ce3d114abd49b1b5baca388d19e929b8f90
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java
index a9d1fa9..152dbc5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java
@@ -14,17 +14,13 @@
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
@@ -33,45 +29,17 @@
}
@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)");
- }
+ public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
+ return super.getLayoutInflater(savedInstanceState).cloneInContext(getContext());
}
@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();
+ return mPluginContext;
}
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
index 47b97bd..9f44bd4 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
@@ -177,8 +177,12 @@
if (DEBUG) Log.d(TAG, "onPluginConnected");
PluginPrefs.setHasPlugins(mContext);
PluginInfo<T> info = (PluginInfo<T>) msg.obj;
- info.mPlugin.onCreate(mContext, info.mPluginContext);
- mListener.onPluginConnected(info.mPlugin);
+ if (!(msg.obj instanceof PluginFragment)) {
+ // Only call onDestroy for plugins that aren't fragments, as fragments
+ // will get the onCreate as part of the fragment lifecycle.
+ info.mPlugin.onCreate(mContext, info.mPluginContext);
+ }
+ mListener.onPluginConnected(info.mPlugin, info.mPluginContext);
break;
case PLUGIN_DISCONNECTED:
if (DEBUG) Log.d(TAG, "onPluginDisconnected");
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
index b2f92d6..b488d2a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
@@ -14,6 +14,8 @@
package com.android.systemui.plugins;
+import android.content.Context;
+
/**
* Interface for listening to plugins being connected.
*/
@@ -24,7 +26,7 @@
* It may also be called in the future if the plugin package changes
* and needs to be reloaded.
*/
- void onPluginConnected(T plugin);
+ void onPluginConnected(T plugin, Context pluginContext);
/**
* Called when a plugin has been uninstalled/updated and should be removed
diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
index efa7cae..efddf20 100644
--- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
@@ -98,7 +98,7 @@
}
@Override
- public void onPluginConnected(ViewProvider plugin) {
+ public void onPluginConnected(ViewProvider plugin, Context context) {
mPluginView = plugin.getView();
inflateLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index f83a5d3..f2aaec1 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -207,7 +207,7 @@
PluginManager.getInstance(this).addPluginListener(OverlayPlugin.ACTION,
new PluginListener<OverlayPlugin>() {
@Override
- public void onPluginConnected(OverlayPlugin plugin) {
+ public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
if (phoneStatusBar != null) {
plugin.setup(phoneStatusBar.getStatusBarWindow(),
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index 57857cc..50506a9 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -27,11 +27,13 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Parcelable;
+import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.SystemUIApplication;
+import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginManager;
import java.io.FileDescriptor;
@@ -47,6 +49,7 @@
private final View mRootView;
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges();
private final FragmentService mManager;
+ private final PluginFragmentManager mPlugins = new PluginFragmentManager();
private FragmentController mFragments;
private FragmentLifecycleCallbacks mLifecycleCallbacks;
@@ -163,6 +166,10 @@
return mFragments.getFragmentManager();
}
+ PluginFragmentManager getPluginManager() {
+ return mPlugins;
+ }
+
public interface FragmentListener {
void onFragmentViewCreated(String tag, Fragment fragment);
@@ -198,6 +205,11 @@
}
@Override
+ public Fragment instantiate(Context context, String className, Bundle arguments) {
+ return mPlugins.instantiate(context, className, arguments);
+ }
+
+ @Override
public boolean onShouldSaveFragmentState(Fragment fragment) {
return true; // True for now.
}
@@ -237,4 +249,57 @@
return true;
}
}
+
+ class PluginFragmentManager {
+ private final ArrayMap<String, Context> mPluginLookup = new ArrayMap<>();
+
+ public void removePlugin(String tag, String currentClass, String defaultClass) {
+ Fragment fragment = getFragmentManager().findFragmentByTag(tag);
+ mPluginLookup.remove(currentClass);
+ getFragmentManager().beginTransaction()
+ .replace(((View) fragment.getView().getParent()).getId(),
+ instantiate(mContext, defaultClass, null), tag)
+ .commit();
+ reloadFragments();
+ }
+
+ public void setCurrentPlugin(String tag, String currentClass, Context context) {
+ Fragment fragment = getFragmentManager().findFragmentByTag(tag);
+ mPluginLookup.put(currentClass, context);
+ getFragmentManager().beginTransaction()
+ .replace(((View) fragment.getView().getParent()).getId(),
+ instantiate(context, currentClass, null), tag)
+ .commit();
+ reloadFragments();
+ }
+
+ private void reloadFragments() {
+ // Save the old state.
+ Parcelable p = destroyFragmentHost();
+ // Generate a new fragment host and restore its state.
+ createFragmentHost(p);
+ }
+
+ Fragment instantiate(Context context, String className, Bundle arguments) {
+ Context pluginContext = mPluginLookup.get(className);
+ if (pluginContext != null) {
+ Fragment f = Fragment.instantiate(pluginContext, className, arguments);
+ if (f instanceof Plugin) {
+ ((Plugin) f).onCreate(mContext, pluginContext);
+ }
+ return f;
+ }
+ return Fragment.instantiate(context, className, arguments);
+ }
+ }
+
+ private static class PluginState {
+ Context mContext;
+ String mCls;
+
+ private PluginState(String cls, Context context) {
+ mCls = cls;
+ mContext = context;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
index e107fcd..2e6de4a 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
@@ -15,6 +15,7 @@
package com.android.systemui.fragments;
import android.app.Fragment;
+import android.content.Context;
import android.util.Log;
import android.view.View;
@@ -30,27 +31,19 @@
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;
+ private final String mTag;
- public PluginFragmentListener(View view, String tag, int id,
- Class<? extends Fragment> defaultFragment,
+ public PluginFragmentListener(View view, String tag, Class<? extends Fragment> defaultFragment,
Class<? extends FragmentBase> expectedInterface) {
+ mTag = tag;
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 */);
}
@@ -58,17 +51,13 @@
mPluginManager.removePluginListener(this);
}
- private void setFragment(Fragment fragment) {
- mFragmentHostManager.getFragmentManager().beginTransaction()
- .replace(mId, fragment, mTag)
- .commit();
- }
-
@Override
- public void onPluginConnected(Plugin plugin) {
+ public void onPluginConnected(Plugin plugin, Context pluginContext) {
try {
mExpectedInterface.cast(plugin);
- setFragment((Fragment) plugin);
+ Fragment.class.cast(plugin);
+ mFragmentHostManager.getPluginManager().setCurrentPlugin(mTag,
+ plugin.getClass().getName(), pluginContext);
} catch (ClassCastException e) {
Log.e(TAG, plugin.getClass().getName() + " must be a Fragment and implement "
+ mExpectedInterface.getName(), e);
@@ -77,10 +66,7 @@
@Override
public void onPluginDisconnected(Plugin plugin) {
- try {
- setFragment(mDefaultClass.newInstance());
- } catch (InstantiationException | IllegalAccessException e) {
- Log.e(TAG, "Couldn't instantiate " + mDefaultClass.getName(), e);
- }
+ mFragmentHostManager.getPluginManager().removePlugin(mTag,
+ plugin.getClass().getName(), mDefaultClass.getName());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index d326787..79120d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -779,7 +779,7 @@
private final PluginListener<IntentButtonProvider> mRightListener =
new PluginListener<IntentButtonProvider>() {
@Override
- public void onPluginConnected(IntentButtonProvider plugin) {
+ public void onPluginConnected(IntentButtonProvider plugin, Context pluginContext) {
setRightButton(plugin.getIntentButton());
}
@@ -792,7 +792,7 @@
private final PluginListener<IntentButtonProvider> mLeftListener =
new PluginListener<IntentButtonProvider>() {
@Override
- public void onPluginConnected(IntentButtonProvider plugin) {
+ public void onPluginConnected(IntentButtonProvider plugin, Context pluginContext) {
setLeftButton(plugin.getIntentButton());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index b6feb0e..f04a9ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -365,7 +365,7 @@
}
@Override
- public void onPluginConnected(NavBarButtonProvider plugin) {
+ public void onPluginConnected(NavBarButtonProvider plugin, Context context) {
mPlugins.add(plugin);
clearViews();
inflateLayout(mCurrentLayout);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 31c78c8f..319f124 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -765,7 +765,7 @@
}
@Override
- public void onPluginConnected(NavGesture plugin) {
+ public void onPluginConnected(NavGesture plugin, Context context) {
mGestureHelper = plugin.getGestureHelper();
updateTaskSwitchHelper();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index cb9fbb9..80ad9d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -870,7 +870,10 @@
View container = mStatusBarWindow.findViewById(R.id.qs_frame);
if (container != null) {
FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
- new PluginFragmentListener(container, QS.TAG, R.id.qs_frame, QSFragment.class, QS.class)
+ fragmentHostManager.getFragmentManager().beginTransaction()
+ .replace(R.id.qs_frame, new QSFragment(), QS.TAG)
+ .commit();
+ new PluginFragmentListener(container, QS.TAG, QSFragment.class, QS.class)
.startListening(QS.ACTION, QS.VERSION);
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
mIconController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
index d529ee1..3715df2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
@@ -113,8 +113,7 @@
waitForIdleSync(mPluginInstanceManager.mPluginHandler);
waitForIdleSync(mPluginInstanceManager.mMainHandler);
- verify(mMockListener, Mockito.never()).onPluginConnected(
- ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener, Mockito.never()).onPluginConnected(any(), any());
}
@Test
@@ -124,7 +123,7 @@
// Verify startup lifecycle
verify(sMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(),
ArgumentCaptor.forClass(Context.class).capture());
- verify(mMockListener).onPluginConnected(ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener).onPluginConnected(any(), any());
}
@Test
@@ -154,8 +153,7 @@
waitForIdleSync(mPluginInstanceManager.mMainHandler);
// Plugin shouldn't be connected because it is the wrong version.
- verify(mMockListener, Mockito.never()).onPluginConnected(
- ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener, Mockito.never()).onPluginConnected(any(), any());
verify(nm).notifyAsUser(eq(TestPlugin.class.getName()), eq(SystemMessage.NOTE_PLUGIN),
any(), eq(UserHandle.ALL));
}
@@ -176,8 +174,7 @@
verify(sMockPlugin, Mockito.times(2)).onCreate(
ArgumentCaptor.forClass(Context.class).capture(),
ArgumentCaptor.forClass(Context.class).capture());
- verify(mMockListener, Mockito.times(2)).onPluginConnected(
- ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener, Mockito.times(2)).onPluginConnected(any(), any());
}
@Test
@@ -193,8 +190,7 @@
waitForIdleSync(mPluginInstanceManager.mMainHandler);;
// Non-debuggable build should receive no plugins.
- verify(mMockListener, Mockito.never()).onPluginConnected(
- ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener, Mockito.never()).onPluginConnected(any(), any());
}
@Test