Merge "SysUI fragments: Integrate new support for constructing"

This commit is contained in:
TreeHugger Robot
2017-01-23 23:01:05 +00:00
committed by Android (Google) Code Review
12 changed files with 102 additions and 78 deletions

View File

@@ -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 @@ public abstract class PluginFragment extends Fragment implements Plugin {
}
@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;
}
}

View File

@@ -177,8 +177,12 @@ public class PluginInstanceManager<T extends Plugin> {
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");

View File

@@ -14,6 +14,8 @@
package com.android.systemui.plugins;
import android.content.Context;
/**
* Interface for listening to plugins being connected.
*/
@@ -24,7 +26,7 @@ public interface PluginListener<T extends Plugin> {
* 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

View File

@@ -98,7 +98,7 @@ public class PluginInflateContainer extends AutoReinflateContainer
}
@Override
public void onPluginConnected(ViewProvider plugin) {
public void onPluginConnected(ViewProvider plugin, Context context) {
mPluginView = plugin.getView();
inflateLayout();
}

View File

@@ -207,7 +207,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv
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(),

View File

@@ -27,11 +27,13 @@ import android.os.Bundle;
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 @@ public class FragmentHostManager {
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 @@ public class FragmentHostManager {
return mFragments.getFragmentManager();
}
PluginFragmentManager getPluginManager() {
return mPlugins;
}
public interface FragmentListener {
void onFragmentViewCreated(String tag, Fragment fragment);
@@ -197,6 +204,11 @@ public class FragmentHostManager {
FragmentHostManager.this.dump(prefix, fd, writer, args);
}
@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 @@ public class FragmentHostManager {
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;
}
}
}

View File

@@ -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 @@ public class PluginFragmentListener implements PluginListener<Plugin> {
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 @@ public class PluginFragmentListener implements PluginListener<Plugin> {
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 @@ public class PluginFragmentListener implements PluginListener<Plugin> {
@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());
}
}

View File

@@ -779,7 +779,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private final PluginListener<IntentButtonProvider> mLeftListener =
new PluginListener<IntentButtonProvider>() {
@Override
public void onPluginConnected(IntentButtonProvider plugin) {
public void onPluginConnected(IntentButtonProvider plugin, Context pluginContext) {
setLeftButton(plugin.getIntentButton());
}

View File

@@ -365,7 +365,7 @@ public class NavigationBarInflaterView extends FrameLayout
}
@Override
public void onPluginConnected(NavBarButtonProvider plugin) {
public void onPluginConnected(NavBarButtonProvider plugin, Context context) {
mPlugins.add(plugin);
clearViews();
inflateLayout(mCurrentLayout);

View File

@@ -765,7 +765,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
}
@Override
public void onPluginConnected(NavGesture plugin) {
public void onPluginConnected(NavGesture plugin, Context context) {
mGestureHelper = plugin.getGestureHelper();
updateTaskSwitchHelper();
}

View File

@@ -870,7 +870,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
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);

View File

@@ -113,8 +113,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
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 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
// 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 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
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 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
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 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
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