From 12d8e155ab07d6e97aa0d09dcbe0633a8bd490e5 Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Thu, 9 Sep 2021 21:17:24 +0000 Subject: [PATCH 1/4] Revert "5/N Remove PluginInitializer entirely." Revert submission 15560568-b194781951-flags-8 Reason for revert: Broken build. Reverted Changes: Ie522ae3cc:5/N Remove PluginInitializer entirely. Icaeaaee28:4/N Remove #handleWtfs from PluginInitializer. I1c86c246c:3/N Remove #getPrivilegedPlugins from PluginInitia... Id525f27e3:2/N Rename PluginInstanceManager to PluginActionMa... Change-Id: I417dd4eddb0216e351a4eb1870a35f3ebc84045a --- .../shared/plugins/PluginActionManager.java | 9 ++++-- .../shared/plugins/PluginInitializer.java | 21 +++++++++++++ .../android/systemui/dagger/PluginModule.java | 30 ++++++++----------- .../plugins/PluginInitializerImpl.java | 29 ++++++++++++++++++ .../systemui/plugins/PluginsModule.java | 8 +++-- .../plugins/PluginActionManagerTest.java | 8 +++-- 6 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java create mode 100644 packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java index 9ea4b578ad3bf..3275d2d804bef 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java @@ -73,6 +73,7 @@ public class PluginActionManager { private final boolean mIsDebuggable; private final PackageManager mPm; private final Class mPluginClass; + private final PluginInitializer mInitializer; private final Executor mMainExecutor; private final Executor mBgExecutor; @@ -86,11 +87,13 @@ public class PluginActionManager { Executor mainExecutor, Executor bgExecutor, boolean debuggable, + PluginInitializer initializer, NotificationManager notificationManager, PluginEnabler pluginEnabler, List privilegedPlugins, PluginInstance.Factory pluginInstanceFactory) { mPluginClass = pluginClass; + mInitializer = initializer; mMainExecutor = mainExecutor; mBgExecutor = bgExecutor; mContext = context; @@ -373,19 +376,21 @@ public class PluginActionManager { private final PackageManager mPackageManager; private final Executor mMainExecutor; private final Executor mBgExecutor; + private final PluginInitializer mInitializer; private final NotificationManager mNotificationManager; private final PluginEnabler mPluginEnabler; private final List mPrivilegedPlugins; private final PluginInstance.Factory mPluginInstanceFactory; public Factory(Context context, PackageManager packageManager, - Executor mainExecutor, Executor bgExecutor, + Executor mainExecutor, Executor bgExecutor, PluginInitializer initializer, NotificationManager notificationManager, PluginEnabler pluginEnabler, List privilegedPlugins, PluginInstance.Factory pluginInstanceFactory) { mContext = context; mPackageManager = packageManager; mMainExecutor = mainExecutor; mBgExecutor = bgExecutor; + mInitializer = initializer; mNotificationManager = notificationManager; mPluginEnabler = pluginEnabler; mPrivilegedPlugins = privilegedPlugins; @@ -397,7 +402,7 @@ public class PluginActionManager { boolean allowMultiple, boolean debuggable) { return new PluginActionManager<>(mContext, mPackageManager, action, listener, pluginClass, allowMultiple, mMainExecutor, mBgExecutor, - debuggable, mNotificationManager, mPluginEnabler, + debuggable, mInitializer, mNotificationManager, mPluginEnabler, mPrivilegedPlugins, mPluginInstanceFactory); } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java new file mode 100644 index 0000000000000..5890f81cc6936 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2018 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.shared.plugins; + +/** + * Provides necessary components for initializing {@link PluginManagerImpl}. + */ +public interface PluginInitializer { +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java index 67ad3db74f8ad..406981d0c4adc 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java @@ -24,7 +24,6 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.GlobalActions; -import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; @@ -33,7 +32,6 @@ import com.android.systemui.volume.VolumeDialogControllerImpl; import dagger.Binds; import dagger.Module; -import dagger.Provides; /** * Module for binding Plugin implementations. @@ -41,40 +39,36 @@ import dagger.Provides; * TODO(b/166258224): Many of these should be moved closer to their implementations. */ @Module -public abstract class PluginModule { - - /** */ - @Provides - static ActivityStarter provideActivityStarter(ActivityStarterDelegate delegate, - PluginDependencyProvider dependencyProvider) { - dependencyProvider.allowPluginDependency(ActivityStarter.class, delegate); - return delegate; - } +public interface PluginModule { /** */ @Binds - abstract DarkIconDispatcher provideDarkIconDispatcher(DarkIconDispatcherImpl controllerImpl); + ActivityStarter provideActivityStarter(ActivityStarterDelegate delegate); /** */ @Binds - abstract FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl); + DarkIconDispatcher provideDarkIconDispatcher(DarkIconDispatcherImpl controllerImpl); /** */ @Binds - abstract GlobalActions provideGlobalActions(GlobalActionsImpl controllerImpl); + FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl); /** */ @Binds - abstract GlobalActions.GlobalActionsManager provideGlobalActionsManager( + GlobalActions provideGlobalActions(GlobalActionsImpl controllerImpl); + + /** */ + @Binds + GlobalActions.GlobalActionsManager provideGlobalActionsManager( GlobalActionsComponent controllerImpl); /** */ @Binds - abstract StatusBarStateController provideStatusBarStateController( + StatusBarStateController provideStatusBarStateController( StatusBarStateControllerImpl controllerImpl); /** */ @Binds - abstract VolumeDialogController provideVolumeDialogController( - VolumeDialogControllerImpl controllerImpl); + VolumeDialogController provideVolumeDialogController(VolumeDialogControllerImpl controllerImpl); + } diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java new file mode 100644 index 0000000000000..8effd53ef56da --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 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 com.android.systemui.shared.plugins.PluginInitializer; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** */ +@Singleton +public class PluginInitializerImpl implements PluginInitializer { + @Inject + public PluginInitializerImpl(PluginDependencyProvider dependencyProvider) { + dependencyProvider.allowPluginDependency(ActivityStarter.class); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java index a0cf44c8bfdcc..11c5efc4ed7db 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java @@ -28,6 +28,7 @@ import com.android.systemui.dagger.PluginModule; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.shared.plugins.PluginActionManager; import com.android.systemui.shared.plugins.PluginEnabler; +import com.android.systemui.shared.plugins.PluginInitializer; import com.android.systemui.shared.plugins.PluginInstance; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.plugins.PluginManagerImpl; @@ -68,6 +69,9 @@ public abstract class PluginsModule { @Binds abstract PluginEnabler bindsPluginEnablerImpl(PluginEnablerImpl impl); + @Binds + abstract PluginInitializer bindsPluginInitializerImpl(PluginInitializerImpl impl); + @Provides @Singleton static PluginInstance.Factory providesPluginInstanceFactory( @@ -85,12 +89,12 @@ public abstract class PluginsModule { @Singleton static PluginActionManager.Factory providePluginInstanceManagerFactory(Context context, PackageManager packageManager, @Main Executor mainExecutor, - @Named(PLUGIN_THREAD) Executor pluginExecutor, + @Named(PLUGIN_THREAD) Executor pluginExecutor, PluginInitializer initializer, NotificationManager notificationManager, PluginEnabler pluginEnabler, @Named(PLUGIN_PRIVILEGED) List privilegedPlugins, PluginInstance.Factory pluginInstanceFactory) { return new PluginActionManager.Factory( - context, packageManager, mainExecutor, pluginExecutor, + context, packageManager, mainExecutor, pluginExecutor, initializer, notificationManager, pluginEnabler, privilegedPlugins, pluginInstanceFactory); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java index 05280fa826ed7..ce366657b7b24 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java @@ -74,6 +74,7 @@ public class PluginActionManagerTest extends SysuiTestCase { private PluginEnabler mMockEnabler; ComponentName mTestPluginComponentName = new ComponentName(PRIVILEGED_PACKAGE, TestPlugin.class.getName()); + private PluginInitializer mInitializer; private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); NotificationManager mNotificationManager; private PluginInstance mPluginInstance; @@ -97,13 +98,14 @@ public class PluginActionManagerTest extends SysuiTestCase { mMockListener = mock(PluginListener.class); mMockEnabler = mock(PluginEnabler.class); mMockVersionInfo = mock(VersionInfo.class); + mInitializer = mock(PluginInitializer.class); mNotificationManager = mock(NotificationManager.class); mMockPlugin = mock(TestPlugin.class); mPluginInstance = mock(PluginInstance.class); when(mPluginInstance.getComponentName()).thenReturn(mTestPluginComponentName); when(mPluginInstance.getPackage()).thenReturn(mTestPluginComponentName.getPackageName()); mActionManagerFactory = new PluginActionManager.Factory(getContext(), mMockPm, - mFakeExecutor, mFakeExecutor, mNotificationManager, mMockEnabler, + mFakeExecutor, mFakeExecutor, mInitializer, mNotificationManager, mMockEnabler, new ArrayList<>(), mPluginInstanceFactory); mPluginActionManager = mActionManagerFactory.create("myAction", mMockListener, @@ -176,7 +178,7 @@ public class PluginActionManagerTest extends SysuiTestCase { public void testNonDebuggable_privileged() throws Exception { // Create a version that thinks the build is not debuggable. PluginActionManager.Factory factory = new PluginActionManager.Factory(getContext(), - mMockPm, mFakeExecutor, mFakeExecutor, mNotificationManager, + mMockPm, mFakeExecutor, mFakeExecutor, mInitializer, mNotificationManager, mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE), mPluginInstanceFactory); mPluginActionManager = factory.create("myAction", mMockListener, @@ -220,7 +222,7 @@ public class PluginActionManagerTest extends SysuiTestCase { @Test public void testDisablePrivileged() throws Exception { PluginActionManager.Factory factory = new PluginActionManager.Factory(getContext(), - mMockPm, mFakeExecutor, mFakeExecutor, mNotificationManager, + mMockPm, mFakeExecutor, mFakeExecutor, mInitializer, mNotificationManager, mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE), mPluginInstanceFactory); mPluginActionManager = factory.create("myAction", mMockListener, From f3a62151d096970945534097dbe50567091468d0 Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Thu, 9 Sep 2021 21:17:24 +0000 Subject: [PATCH 2/4] Revert "4/N Remove #handleWtfs from PluginInitializer." Revert submission 15560568-b194781951-flags-8 Reason for revert: Broken build. Reverted Changes: Ie522ae3cc:5/N Remove PluginInitializer entirely. Icaeaaee28:4/N Remove #handleWtfs from PluginInitializer. I1c86c246c:3/N Remove #getPrivilegedPlugins from PluginInitia... Id525f27e3:2/N Rename PluginInstanceManager to PluginActionMa... Change-Id: Ia5f864646ee8806ec4080349dda3d71c827c1e40 --- .../shared/plugins/PluginActionManager.java | 1 + .../shared/plugins/PluginInitializer.java | 5 +++++ .../plugins/PluginInitializerImpl.java | 20 +++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java index 3275d2d804bef..af541f07fb68a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java @@ -212,6 +212,7 @@ public class PluginActionManager { private void onPluginConnected(PluginInstance pluginInstance) { if (DEBUG) Log.d(TAG, "onPluginConnected"); PluginPrefs.setHasPlugins(mContext); + mInitializer.handleWtfs(); pluginInstance.onCreate(mContext, mListener); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java index 5890f81cc6936..868f22ee4bed6 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java @@ -18,4 +18,9 @@ package com.android.systemui.shared.plugins; * Provides necessary components for initializing {@link PluginManagerImpl}. */ public interface PluginInitializer { + + /** + * Called from {@link PluginActionManager}. + */ + void handleWtfs(); } diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java index 8effd53ef56da..09af98ea5244e 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java @@ -14,7 +14,10 @@ package com.android.systemui.plugins; +import android.util.Log; + import com.android.systemui.shared.plugins.PluginInitializer; +import com.android.systemui.shared.plugins.PluginManagerImpl; import javax.inject.Inject; import javax.inject.Singleton; @@ -22,8 +25,25 @@ import javax.inject.Singleton; /** */ @Singleton public class PluginInitializerImpl implements PluginInitializer { + + /** + * True if WTFs should lead to crashes + */ + private static final boolean WTFS_SHOULD_CRASH = false; + private boolean mWtfsSet; + @Inject public PluginInitializerImpl(PluginDependencyProvider dependencyProvider) { dependencyProvider.allowPluginDependency(ActivityStarter.class); } + + @Override + public void handleWtfs() { + if (WTFS_SHOULD_CRASH && !mWtfsSet) { + mWtfsSet = true; + Log.setWtfHandler((tag, what, system) -> { + throw new PluginManagerImpl.CrashWhilePluginActiveException(what); + }); + } + } } From 6ae34f5d605fe857de12098ddcbbcd2a312bd3c9 Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Thu, 9 Sep 2021 21:17:24 +0000 Subject: [PATCH 3/4] Revert "3/N Remove #getPrivilegedPlugins from PluginInitializer." Revert submission 15560568-b194781951-flags-8 Reason for revert: Broken build. Reverted Changes: Ie522ae3cc:5/N Remove PluginInitializer entirely. Icaeaaee28:4/N Remove #handleWtfs from PluginInitializer. I1c86c246c:3/N Remove #getPrivilegedPlugins from PluginInitia... Id525f27e3:2/N Rename PluginInstanceManager to PluginActionMa... Change-Id: I70f22b6a51a13f967f627e0b47c0f20c18fb4eb1 --- .../systemui/shared/plugins/PluginInitializer.java | 8 ++++++++ .../android/systemui/plugins/PluginInitializerImpl.java | 8 ++++++++ .../src/com/android/systemui/plugins/PluginsModule.java | 5 ++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java index 868f22ee4bed6..f0e0320e165f1 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java @@ -14,11 +14,19 @@ package com.android.systemui.shared.plugins; +import android.content.Context; + /** * Provides necessary components for initializing {@link PluginManagerImpl}. */ public interface PluginInitializer { + /** + * Return a list of plugins that don't get disabled when an exception occurs. + */ + String[] getPrivilegedPlugins(Context context); + + /** * Called from {@link PluginActionManager}. */ diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java index 09af98ea5244e..654d000ca09e9 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java @@ -14,8 +14,10 @@ package com.android.systemui.plugins; +import android.content.Context; import android.util.Log; +import com.android.systemui.R; import com.android.systemui.shared.plugins.PluginInitializer; import com.android.systemui.shared.plugins.PluginManagerImpl; @@ -37,6 +39,12 @@ public class PluginInitializerImpl implements PluginInitializer { dependencyProvider.allowPluginDependency(ActivityStarter.class); } + @Override + public String[] getPrivilegedPlugins(Context context) { + return context.getResources().getStringArray(R.array.config_pluginWhitelist); + } + + @Override public void handleWtfs() { if (WTFS_SHOULD_CRASH && !mWtfsSet) { diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java index 11c5efc4ed7db..6a93fb7e4861a 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java @@ -23,7 +23,6 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; -import com.android.systemui.R; import com.android.systemui.dagger.PluginModule; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.shared.plugins.PluginActionManager; @@ -127,7 +126,7 @@ public abstract class PluginsModule { @Provides @Named(PLUGIN_PRIVILEGED) - static List providesPrivilegedPlugins(Context context) { - return Arrays.asList(context.getResources().getStringArray(R.array.config_pluginWhitelist)); + static List providesPrivilegedPlugins(PluginInitializer initializer, Context context) { + return Arrays.asList(initializer.getPrivilegedPlugins(context)); } } From be86cc2d0be928d3b6134a25d8275a1dbd3f94ca Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Thu, 9 Sep 2021 21:17:24 +0000 Subject: [PATCH 4/4] Revert "2/N Rename PluginInstanceManager to PluginActionManager." Revert submission 15560568-b194781951-flags-8 Reason for revert: Broken build. Reverted Changes: Ie522ae3cc:5/N Remove PluginInitializer entirely. Icaeaaee28:4/N Remove #handleWtfs from PluginInitializer. I1c86c246c:3/N Remove #getPrivilegedPlugins from PluginInitia... Id525f27e3:2/N Rename PluginInstanceManager to PluginActionMa... Change-Id: I07a26c9ff526d6c2af194d53282cc8133371efb1 --- .../shared/plugins/PluginActionManager.java | 438 --------------- .../shared/plugins/PluginInitializer.java | 2 +- .../shared/plugins/PluginInstance.java | 228 -------- .../shared/plugins/PluginInstanceManager.java | 513 ++++++++++++++++++ .../shared/plugins/PluginManager.java | 10 +- .../shared/plugins/PluginManagerImpl.java | 40 +- .../systemui/shared/plugins/VersionInfo.java | 12 - .../systemui/PluginInflateContainer.java | 4 +- .../systemui/plugins/PluginsModule.java | 28 +- .../systemui/tuner/PluginFragment.java | 4 +- ...st.java => PluginInstanceManagerTest.java} | 136 +++-- .../shared/plugins/PluginInstanceTest.java | 138 ----- .../shared/plugins/PluginManagerTest.java | 21 +- .../utils/leaks/FakePluginManager.java | 8 +- 14 files changed, 637 insertions(+), 945 deletions(-) delete mode 100644 packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java delete mode 100644 packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java create mode 100644 packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java rename packages/SystemUI/tests/src/com/android/systemui/shared/plugins/{PluginActionManagerTest.java => PluginInstanceManagerTest.java} (65%) delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java deleted file mode 100644 index af541f07fb68a..0000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java +++ /dev/null @@ -1,438 +0,0 @@ -/* - * 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.shared.plugins; - -import android.app.Notification; -import android.app.Notification.Action; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.ContextWrapper; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; -import android.content.res.Resources; -import android.net.Uri; -import android.util.ArraySet; -import android.util.Log; -import android.view.LayoutInflater; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.systemui.plugins.Plugin; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executor; - -/** - * Coordinates all the available plugins for a given action. - * - * The available plugins are queried from the {@link PackageManager} via an an {@link Intent} - * action. - * - * @param The type of plugin that this contains. - */ -public class PluginActionManager { - - private static final boolean DEBUG = false; - - private static final String TAG = "PluginInstanceManager"; - public static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN"; - - private final Context mContext; - private final PluginListener mListener; - private final String mAction; - private final boolean mAllowMultiple; - private final NotificationManager mNotificationManager; - private final PluginEnabler mPluginEnabler; - private final PluginInstance.Factory mPluginInstanceFactory; - private final ArraySet mPrivilegedPlugins = new ArraySet<>(); - - @VisibleForTesting - private final ArrayList> mPluginInstances = new ArrayList<>(); - private final boolean mIsDebuggable; - private final PackageManager mPm; - private final Class mPluginClass; - private final PluginInitializer mInitializer; - private final Executor mMainExecutor; - private final Executor mBgExecutor; - - private PluginActionManager( - Context context, - PackageManager pm, - String action, - PluginListener listener, - Class pluginClass, - boolean allowMultiple, - Executor mainExecutor, - Executor bgExecutor, - boolean debuggable, - PluginInitializer initializer, - NotificationManager notificationManager, - PluginEnabler pluginEnabler, - List privilegedPlugins, - PluginInstance.Factory pluginInstanceFactory) { - mPluginClass = pluginClass; - mInitializer = initializer; - mMainExecutor = mainExecutor; - mBgExecutor = bgExecutor; - mContext = context; - mPm = pm; - mAction = action; - mListener = listener; - mAllowMultiple = allowMultiple; - mNotificationManager = notificationManager; - mPluginEnabler = pluginEnabler; - mPluginInstanceFactory = pluginInstanceFactory; - mPrivilegedPlugins.addAll(privilegedPlugins); - mIsDebuggable = debuggable; - } - - /** Load all plugins matching this instance's action. */ - public void loadAll() { - if (DEBUG) Log.d(TAG, "startListening"); - mBgExecutor.execute(this::queryAll); - } - - /** Unload all plugins managed by this instance. */ - public void destroy() { - if (DEBUG) Log.d(TAG, "stopListening"); - ArrayList> plugins = new ArrayList<>(mPluginInstances); - for (PluginInstance plugInstance : plugins) { - mMainExecutor.execute(() -> onPluginDisconnected(plugInstance)); - } - } - - /** Unload all matching plugins managed by this instance. */ - public void onPackageRemoved(String pkg) { - mBgExecutor.execute(() -> removePkg(pkg)); - } - - /** Unload and then reload all matching plugins managed by this instance. */ - public void reloadPackage(String pkg) { - mBgExecutor.execute(() -> { - removePkg(pkg); - queryPkg(pkg); - }); - } - - /** Disable a specific plugin managed by this instance. */ - public boolean checkAndDisable(String className) { - boolean disableAny = false; - ArrayList> plugins = new ArrayList<>(mPluginInstances); - for (PluginInstance info : plugins) { - if (className.startsWith(info.getPackage())) { - disableAny |= disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH); - } - } - return disableAny; - } - - /** Disable all plugins managed by this instance. */ - public boolean disableAll() { - ArrayList> plugins = new ArrayList<>(mPluginInstances); - boolean disabledAny = false; - for (int i = 0; i < plugins.size(); i++) { - disabledAny |= disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH); - } - return disabledAny; - } - - boolean isPluginPrivileged(ComponentName pluginName) { - for (String componentNameOrPackage : mPrivilegedPlugins) { - ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage); - if (componentName == null) { - if (componentNameOrPackage.equals(pluginName.getPackageName())) { - return true; - } - } else { - if (componentName.equals(pluginName)) { - return true; - } - } - } - return false; - } - - private boolean disable( - PluginInstance pluginInstance, @PluginEnabler.DisableReason int reason) { - // Live by the sword, die by the sword. - // Misbehaving plugins get disabled and won't come back until uninstall/reinstall. - - ComponentName pluginComponent = pluginInstance.getComponentName(); - // If a plugin is detected in the stack of a crash then this will be called for that - // plugin, if the plugin causing a crash cannot be identified, they are all disabled - // assuming one of them must be bad. - if (isPluginPrivileged(pluginComponent)) { - // Don't disable privileged plugins as they are a part of the OS. - return false; - } - Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString()); - mPluginEnabler.setDisabled(pluginComponent, reason); - - return true; - } - - boolean dependsOn(Plugin p, Class cls) { - ArrayList> instances = new ArrayList<>(mPluginInstances); - for (PluginInstance instance : instances) { - if (instance.containsPluginClass(p.getClass())) { - return instance.getVersionInfo() != null && instance.getVersionInfo().hasClass(cls); - } - } - return false; - } - - @Override - public String toString() { - return String.format("%s@%s (action=%s)", - getClass().getSimpleName(), hashCode(), mAction); - } - - private void onPluginConnected(PluginInstance pluginInstance) { - if (DEBUG) Log.d(TAG, "onPluginConnected"); - PluginPrefs.setHasPlugins(mContext); - mInitializer.handleWtfs(); - pluginInstance.onCreate(mContext, mListener); - } - - private void onPluginDisconnected(PluginInstance pluginInstance) { - if (DEBUG) Log.d(TAG, "onPluginDisconnected"); - pluginInstance.onDestroy(mListener); - } - - private void queryAll() { - if (DEBUG) Log.d(TAG, "queryAll " + mAction); - for (int i = mPluginInstances.size() - 1; i >= 0; i--) { - PluginInstance pluginInstance = mPluginInstances.get(i); - mMainExecutor.execute(() -> onPluginDisconnected(pluginInstance)); - } - mPluginInstances.clear(); - handleQueryPlugins(null); - } - - private void removePkg(String pkg) { - for (int i = mPluginInstances.size() - 1; i >= 0; i--) { - final PluginInstance pluginInstance = mPluginInstances.get(i); - if (pluginInstance.getPackage().equals(pkg)) { - mMainExecutor.execute(() -> onPluginDisconnected(pluginInstance)); - mPluginInstances.remove(i); - } - } - } - - private void queryPkg(String pkg) { - if (DEBUG) Log.d(TAG, "queryPkg " + pkg + " " + mAction); - if (mAllowMultiple || (mPluginInstances.size() == 0)) { - handleQueryPlugins(pkg); - } else { - if (DEBUG) Log.d(TAG, "Too many of " + mAction); - } - } - - private void handleQueryPlugins(String pkgName) { - // This isn't actually a service and shouldn't ever be started, but is - // a convenient PM based way to manage our plugins. - Intent intent = new Intent(mAction); - if (pkgName != null) { - intent.setPackage(pkgName); - } - List result = mPm.queryIntentServices(intent, 0); - if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins"); - if (result.size() > 1 && !mAllowMultiple) { - // TODO: Show warning. - Log.w(TAG, "Multiple plugins found for " + mAction); - if (DEBUG) { - for (ResolveInfo info : result) { - ComponentName name = new ComponentName(info.serviceInfo.packageName, - info.serviceInfo.name); - Log.w(TAG, " " + name); - } - } - return; - } - for (ResolveInfo info : result) { - ComponentName name = new ComponentName(info.serviceInfo.packageName, - info.serviceInfo.name); - PluginInstance pluginInstance = loadPluginComponent(name); - if (pluginInstance != null) { - // add plugin before sending PLUGIN_CONNECTED message - mPluginInstances.add(pluginInstance); - mMainExecutor.execute(() -> onPluginConnected(pluginInstance)); - } - } - } - - private PluginInstance loadPluginComponent(ComponentName component) { - // This was already checked, but do it again here to make extra extra sure, we don't - // use these on production builds. - if (!mIsDebuggable && !isPluginPrivileged(component)) { - // Never ever ever allow these on production builds, they are only for prototyping. - Log.w(TAG, "Plugin cannot be loaded on production build: " + component); - return null; - } - if (!mPluginEnabler.isEnabled(component)) { - if (DEBUG) { - Log.d(TAG, "Plugin is not enabled, aborting load: " + component); - } - return null; - } - String packageName = component.getPackageName(); - try { - // TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on - if (mPm.checkPermission(PLUGIN_PERMISSION, packageName) - != PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Plugin doesn't have permission: " + packageName); - return null; - } - - ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, 0); - // TODO: Only create the plugin before version check if we need it for - // legacy version check. - if (DEBUG) { - Log.d(TAG, "createPlugin"); - } - try { - return mPluginInstanceFactory.create( - mContext, appInfo, component, - mPluginClass); - } catch (InvalidVersionException e) { - reportInvalidVersion(component, component.getClassName(), e); - } - } catch (Throwable e) { - Log.w(TAG, "Couldn't load plugin: " + packageName, e); - return null; - } - - return null; - } - - private void reportInvalidVersion( - ComponentName component, String className, InvalidVersionException e) { - final int icon = Resources.getSystem().getIdentifier( - "stat_sys_warning", "drawable", "android"); - final int color = Resources.getSystem().getIdentifier( - "system_notification_accent_color", "color", "android"); - final Notification.Builder nb = new Notification.Builder(mContext, - PluginManager.NOTIFICATION_CHANNEL_ID) - .setStyle(new Notification.BigTextStyle()) - .setSmallIcon(icon) - .setWhen(0) - .setShowWhen(false) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(mContext.getColor(color)); - String label = className; - try { - label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString(); - } catch (NameNotFoundException e2) { - // no-op - } - if (!e.isTooNew()) { - // Localization not required as this will never ever appear in a user build. - nb.setContentTitle("Plugin \"" + label + "\" is too old") - .setContentText("Contact plugin developer to get an updated" - + " version.\n" + e.getMessage()); - } else { - // Localization not required as this will never ever appear in a user build. - nb.setContentTitle("Plugin \"" + label + "\" is too new") - .setContentText("Check to see if an OTA is available.\n" - + e.getMessage()); - } - Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData( - Uri.parse("package://" + component.flattenToString())); - PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, - PendingIntent.FLAG_IMMUTABLE); - nb.addAction(new Action.Builder(null, "Disable plugin", pi).build()); - mNotificationManager.notify(SystemMessage.NOTE_PLUGIN, nb.build()); - // TODO: Warn user. - Log.w(TAG, "Plugin has invalid interface version " + e.getActualVersion() - + ", expected " + e.getExpectedVersion()); - } - - /** - * Construct a {@link PluginActionManager} - */ - public static class Factory { - private final Context mContext; - private final PackageManager mPackageManager; - private final Executor mMainExecutor; - private final Executor mBgExecutor; - private final PluginInitializer mInitializer; - private final NotificationManager mNotificationManager; - private final PluginEnabler mPluginEnabler; - private final List mPrivilegedPlugins; - private final PluginInstance.Factory mPluginInstanceFactory; - - public Factory(Context context, PackageManager packageManager, - Executor mainExecutor, Executor bgExecutor, PluginInitializer initializer, - NotificationManager notificationManager, PluginEnabler pluginEnabler, - List privilegedPlugins, PluginInstance.Factory pluginInstanceFactory) { - mContext = context; - mPackageManager = packageManager; - mMainExecutor = mainExecutor; - mBgExecutor = bgExecutor; - mInitializer = initializer; - mNotificationManager = notificationManager; - mPluginEnabler = pluginEnabler; - mPrivilegedPlugins = privilegedPlugins; - mPluginInstanceFactory = pluginInstanceFactory; - } - - PluginActionManager create( - String action, PluginListener listener, Class pluginClass, - boolean allowMultiple, boolean debuggable) { - return new PluginActionManager<>(mContext, mPackageManager, action, listener, - pluginClass, allowMultiple, mMainExecutor, mBgExecutor, - debuggable, mInitializer, mNotificationManager, mPluginEnabler, - mPrivilegedPlugins, mPluginInstanceFactory); - } - } - - /** */ - public static class PluginContextWrapper extends ContextWrapper { - private final ClassLoader mClassLoader; - private LayoutInflater mInflater; - - public PluginContextWrapper(Context base, ClassLoader classLoader) { - super(base); - mClassLoader = classLoader; - } - - @Override - public ClassLoader getClassLoader() { - return mClassLoader; - } - - @Override - public Object getSystemService(String name) { - if (LAYOUT_INFLATER_SERVICE.equals(name)) { - if (mInflater == null) { - mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this); - } - return mInflater; - } - return getBaseContext().getSystemService(name); - } - } - -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java index f0e0320e165f1..895b6cd96d6fe 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java @@ -28,7 +28,7 @@ public interface PluginInitializer { /** - * Called from {@link PluginActionManager}. + * Called from {@link PluginInstanceManager}. */ void handleWtfs(); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java deleted file mode 100644 index 2f84602089e0e..0000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2021 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.shared.plugins; - -import android.app.LoadedApk; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.text.TextUtils; -import android.util.ArrayMap; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.plugins.Plugin; -import com.android.systemui.plugins.PluginFragment; -import com.android.systemui.plugins.PluginListener; - -import dalvik.system.PathClassLoader; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * Contains a single instantiation of a Plugin. - * - * This class and its related Factory are in charge of actually instantiating a plugin and - * managing any state related to it. - * - * @param The type of plugin that this contains. - */ -public class PluginInstance { - private static final String TAG = "PluginInstance"; - private static final Map sClassLoaders = new ArrayMap<>(); - - private final Context mPluginContext; - private final VersionInfo mVersionInfo; - private final ComponentName mComponentName; - private final T mPlugin; - - /** */ - public PluginInstance(ComponentName componentName, T plugin, Context pluginContext, - VersionInfo versionInfo) { - mComponentName = componentName; - mPlugin = plugin; - mPluginContext = pluginContext; - mVersionInfo = versionInfo; - } - - /** Alerts listener and plugin that the plugin has been created. */ - public void onCreate(Context appContext, PluginListener listener) { - if (!(mPlugin instanceof PluginFragment)) { - // Only call onCreate for plugins that aren't fragments, as fragments - // will get the onCreate as part of the fragment lifecycle. - mPlugin.onCreate(appContext, mPluginContext); - } - listener.onPluginConnected(mPlugin, mPluginContext); - } - - /** Alerts listener and plugin that the plugin is being shutdown. */ - public void onDestroy(PluginListener listener) { - listener.onPluginDisconnected(mPlugin); - if (!(mPlugin instanceof PluginFragment)) { - // Only call onDestroy for plugins that aren't fragments, as fragments - // will get the onDestroy as part of the fragment lifecycle. - mPlugin.onDestroy(); - } - } - - /** - * Returns if the contained plugin matches the passed in class name. - * - * It does this by string comparison of the class names. - **/ - public boolean containsPluginClass(Class pluginClass) { - return mPlugin.getClass().getName().equals(pluginClass.getName()); - } - - public ComponentName getComponentName() { - return mComponentName; - } - - public String getPackage() { - return mComponentName.getPackageName(); - } - - public VersionInfo getVersionInfo() { - return mVersionInfo; - } - - @VisibleForTesting - Context getPluginContext() { - return mPluginContext; - } - - /** Used to create new {@link PluginInstance}s. */ - public static class Factory { - private final ClassLoader mBaseClassLoader; - private final InstanceFactory mInstanceFactory; - private final VersionChecker mVersionChecker; - private final boolean mIsDebug; - private final List mPrivilegedPlugins; - - /** Factory used to construct {@link PluginInstance}s. */ - public Factory(ClassLoader classLoader, InstanceFactory instanceFactory, - VersionChecker versionChecker, - List privilegedPlugins, - boolean isDebug) { - mPrivilegedPlugins = privilegedPlugins; - mBaseClassLoader = classLoader; - mInstanceFactory = instanceFactory; - mVersionChecker = versionChecker; - mIsDebug = isDebug; - } - - /** Construct a new PluginInstance. */ - public PluginInstance create( - Context context, - ApplicationInfo appInfo, - ComponentName componentName, - Class pluginClass) - throws PackageManager.NameNotFoundException, ClassNotFoundException, - InstantiationException, IllegalAccessException { - - ClassLoader classLoader = getClassLoader(appInfo, mBaseClassLoader); - Context pluginContext = new PluginActionManager.PluginContextWrapper( - context.createApplicationContext(appInfo, 0), classLoader); - Class instanceClass = (Class) Class.forName( - componentName.getClassName(), true, classLoader); - // TODO: Only create the plugin before version check if we need it for - // legacy version check. - T instance = (T) mInstanceFactory.create(instanceClass); - VersionInfo version = mVersionChecker.checkVersion( - instanceClass, pluginClass, instance); - return new PluginInstance(componentName, instance, pluginContext, version); - } - - private boolean isPluginPackagePrivileged(String packageName) { - for (String componentNameOrPackage : mPrivilegedPlugins) { - ComponentName componentName = ComponentName.unflattenFromString( - componentNameOrPackage); - if (componentName != null) { - if (componentName.getPackageName().equals(packageName)) { - return true; - } - } else if (componentNameOrPackage.equals(packageName)) { - return true; - } - } - return false; - } - - private ClassLoader getParentClassLoader(ClassLoader baseClassLoader) { - return new PluginManagerImpl.ClassLoaderFilter( - baseClassLoader, "com.android.systemui.plugin"); - } - - /** Returns class loader specific for the given plugin. */ - private ClassLoader getClassLoader(ApplicationInfo appInfo, - ClassLoader baseClassLoader) { - if (!mIsDebug && !isPluginPackagePrivileged(appInfo.packageName)) { - Log.w(TAG, "Cannot get class loader for non-privileged plugin. Src:" - + appInfo.sourceDir + ", pkg: " + appInfo.packageName); - return null; - } - if (sClassLoaders.containsKey(appInfo.packageName)) { - return sClassLoaders.get(appInfo.packageName); - } - - List zipPaths = new ArrayList<>(); - List libPaths = new ArrayList<>(); - LoadedApk.makePaths(null, true, appInfo, zipPaths, libPaths); - ClassLoader classLoader = new PathClassLoader( - TextUtils.join(File.pathSeparator, zipPaths), - TextUtils.join(File.pathSeparator, libPaths), - getParentClassLoader(baseClassLoader)); - sClassLoaders.put(appInfo.packageName, classLoader); - return classLoader; - } - } - - /** Class that compares a plugin class against an implementation for version matching. */ - public static class VersionChecker { - /** Compares two plugin classes. */ - public VersionInfo checkVersion( - Class instanceClass, Class pluginClass, Plugin plugin) { - VersionInfo pluginVersion = new VersionInfo().addClass(pluginClass); - VersionInfo instanceVersion = new VersionInfo().addClass(instanceClass); - if (instanceVersion.hasVersionInfo()) { - pluginVersion.checkVersion(instanceVersion); - } else { - int fallbackVersion = plugin.getVersion(); - if (fallbackVersion != pluginVersion.getDefaultVersion()) { - throw new VersionInfo.InvalidVersionException("Invalid legacy version", false); - } - return null; - } - return instanceVersion; - } - } - - /** - * Simple class to create a new instance. Useful for testing. - * - * @param The type of plugin this create. - **/ - public static class InstanceFactory { - T create(Class cls) throws IllegalAccessException, InstantiationException { - return (T) cls.newInstance(); - } - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java new file mode 100644 index 0000000000000..dcd3b3eb5dd41 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java @@ -0,0 +1,513 @@ +/* + * 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.shared.plugins; + +import android.app.LoadedApk; +import android.app.Notification; +import android.app.Notification.Action; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.net.Uri; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; +import android.view.LayoutInflater; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.PluginFragment; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException; + +import dalvik.system.PathClassLoader; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +public class PluginInstanceManager { + + private static final boolean DEBUG = false; + + private static final String TAG = "PluginInstanceManager"; + public static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN"; + + private final Context mContext; + private final PluginListener mListener; + private final String mAction; + private final boolean mAllowMultiple; + private final VersionInfo mVersion; + private final NotificationManager mNotificationManager; + private final PluginEnabler mPluginEnabler; + private final InstanceFactory mInstanceFactory; + private final ArraySet mPrivilegedPlugins = new ArraySet<>(); + private final Map mClassLoaders = new ArrayMap<>(); + + @VisibleForTesting + private final ArrayList> mPlugins = new ArrayList<>(); + private final boolean mIsDebuggable; + private final PackageManager mPm; + private final PluginInitializer mInitializer; + private final Executor mMainExecutor; + private final Executor mBgExecutor; + + private PluginManagerImpl.ClassLoaderFilter mParentClassLoader; + + private PluginInstanceManager(Context context, PackageManager pm, String action, + PluginListener listener, boolean allowMultiple, Executor mainExecutor, + Executor bgExecutor, VersionInfo version, boolean debuggable, + PluginInitializer initializer, NotificationManager notificationManager, + PluginEnabler pluginEnabler, List privilegedPlugins, + InstanceFactory instanceFactory) { + mInitializer = initializer; + mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; + mContext = context; + mPm = pm; + mAction = action; + mListener = listener; + mAllowMultiple = allowMultiple; + mVersion = version; + mNotificationManager = notificationManager; + mPluginEnabler = pluginEnabler; + mInstanceFactory = instanceFactory; + mPrivilegedPlugins.addAll(privilegedPlugins); + mIsDebuggable = debuggable; + } + + public void loadAll() { + if (DEBUG) Log.d(TAG, "startListening"); + mBgExecutor.execute(this::queryAll); + } + + public void destroy() { + if (DEBUG) Log.d(TAG, "stopListening"); + ArrayList> plugins = new ArrayList<>(mPlugins); + for (PluginInfo pluginInfo : plugins) { + mMainExecutor.execute(() -> onPluginDisconnected(pluginInfo.mPlugin)); + } + } + + public void onPackageRemoved(String pkg) { + mBgExecutor.execute(() -> removePkg(pkg)); + } + + public void onPackageChange(String pkg) { + mBgExecutor.execute(() -> removePkg(pkg)); + mBgExecutor.execute(() -> queryPkg(pkg)); + } + + public boolean checkAndDisable(String className) { + boolean disableAny = false; + ArrayList> plugins = new ArrayList<>(mPlugins); + for (PluginInfo info : plugins) { + if (className.startsWith(info.mPackage)) { + disableAny |= disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH); + } + } + return disableAny; + } + + public boolean disableAll() { + ArrayList> plugins = new ArrayList<>(mPlugins); + boolean disabledAny = false; + for (int i = 0; i < plugins.size(); i++) { + disabledAny |= disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH); + } + return disabledAny; + } + + private boolean isPluginPackagePrivileged(String packageName) { + for (String componentNameOrPackage : mPrivilegedPlugins) { + ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage); + if (componentName != null) { + if (componentName.getPackageName().equals(packageName)) { + return true; + } + } else if (componentNameOrPackage.equals(packageName)) { + return true; + } + } + return false; + } + + private boolean isPluginPrivileged(ComponentName pluginName) { + for (String componentNameOrPackage : mPrivilegedPlugins) { + ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage); + if (componentName == null) { + if (componentNameOrPackage.equals(pluginName.getPackageName())) { + return true; + } + } else { + if (componentName.equals(pluginName)) { + return true; + } + } + } + return false; + } + + private boolean disable(PluginInfo info, @PluginEnabler.DisableReason int reason) { + // Live by the sword, die by the sword. + // Misbehaving plugins get disabled and won't come back until uninstall/reinstall. + + ComponentName pluginComponent = new ComponentName(info.mPackage, info.mClass); + // If a plugin is detected in the stack of a crash then this will be called for that + // plugin, if the plugin causing a crash cannot be identified, they are all disabled + // assuming one of them must be bad. + if (isPluginPrivileged(pluginComponent)) { + // Don't disable whitelisted plugins as they are a part of the OS. + return false; + } + Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString()); + mPluginEnabler.setDisabled(pluginComponent, reason); + + return true; + } + + boolean dependsOn(Plugin p, Class cls) { + ArrayList> plugins = new ArrayList<>(mPlugins); + for (PluginInfo info : plugins) { + if (info.mPlugin.getClass().getName().equals(p.getClass().getName())) { + return info.mVersion != null && info.mVersion.hasClass(cls); + } + } + return false; + } + + @Override + public String toString() { + return String.format("%s@%s (action=%s)", + getClass().getSimpleName(), hashCode(), mAction); + } + + private void onPluginConnected(PluginInfo pluginInfo) { + if (DEBUG) Log.d(TAG, "onPluginConnected"); + PluginPrefs.setHasPlugins(mContext); + mInitializer.handleWtfs(); + if (!(pluginInfo.mPlugin instanceof PluginFragment)) { + // Only call onCreate for plugins that aren't fragments, as fragments + // will get the onCreate as part of the fragment lifecycle. + pluginInfo.mPlugin.onCreate(mContext, pluginInfo.mPluginContext); + } + mListener.onPluginConnected(pluginInfo.mPlugin, pluginInfo.mPluginContext); + } + + private void onPluginDisconnected(T plugin) { + if (DEBUG) Log.d(TAG, "onPluginDisconnected"); + mListener.onPluginDisconnected(plugin); + if (!(plugin instanceof PluginFragment)) { + // Only call onDestroy for plugins that aren't fragments, as fragments + // will get the onDestroy as part of the fragment lifecycle. + plugin.onDestroy(); + } + } + + private void queryAll() { + if (DEBUG) Log.d(TAG, "queryAll " + mAction); + for (int i = mPlugins.size() - 1; i >= 0; i--) { + PluginInfo pluginInfo = mPlugins.get(i); + mMainExecutor.execute(() -> onPluginDisconnected(pluginInfo.mPlugin)); + } + mPlugins.clear(); + handleQueryPlugins(null); + } + + private void removePkg(String pkg) { + for (int i = mPlugins.size() - 1; i >= 0; i--) { + final PluginInfo pluginInfo = mPlugins.get(i); + if (pluginInfo.mPackage.equals(pkg)) { + mMainExecutor.execute(() -> onPluginDisconnected(pluginInfo.mPlugin)); + mPlugins.remove(i); + } + } + } + + private void queryPkg(String pkg) { + if (DEBUG) Log.d(TAG, "queryPkg " + pkg + " " + mAction); + if (mAllowMultiple || (mPlugins.size() == 0)) { + handleQueryPlugins(pkg); + } else { + if (DEBUG) Log.d(TAG, "Too many of " + mAction); + } + } + + private void handleQueryPlugins(String pkgName) { + // This isn't actually a service and shouldn't ever be started, but is + // a convenient PM based way to manage our plugins. + Intent intent = new Intent(mAction); + if (pkgName != null) { + intent.setPackage(pkgName); + } + List result = mPm.queryIntentServices(intent, 0); + if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins"); + if (result.size() > 1 && !mAllowMultiple) { + // TODO: Show warning. + Log.w(TAG, "Multiple plugins found for " + mAction); + if (DEBUG) { + for (ResolveInfo info : result) { + ComponentName name = new ComponentName(info.serviceInfo.packageName, + info.serviceInfo.name); + Log.w(TAG, " " + name); + } + } + return; + } + for (ResolveInfo info : result) { + ComponentName name = new ComponentName(info.serviceInfo.packageName, + info.serviceInfo.name); + PluginInfo pluginInfo = handleLoadPlugin(name); + if (pluginInfo == null) continue; + + // add plugin before sending PLUGIN_CONNECTED message + mPlugins.add(pluginInfo); + mMainExecutor.execute(() -> onPluginConnected(pluginInfo)); + } + } + + protected PluginInfo handleLoadPlugin(ComponentName component) { + // This was already checked, but do it again here to make extra extra sure, we don't + // use these on production builds. + if (!mIsDebuggable && !isPluginPrivileged(component)) { + // Never ever ever allow these on production builds, they are only for prototyping. + Log.w(TAG, "Plugin cannot be loaded on production build: " + component); + return null; + } + if (!mPluginEnabler.isEnabled(component)) { + if (DEBUG) Log.d(TAG, "Plugin is not enabled, aborting load: " + component); + return null; + } + String pkg = component.getPackageName(); + String cls = component.getClassName(); + try { + ApplicationInfo info = mPm.getApplicationInfo(pkg, 0); + // TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on + if (mPm.checkPermission(PLUGIN_PERMISSION, pkg) + != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Plugin doesn't have permission: " + pkg); + return null; + } + // Create our own ClassLoader so we can use our own code as the parent. + ClassLoader classLoader = getClassLoader(info); + Context pluginContext = new PluginContextWrapper( + mContext.createApplicationContext(info, 0), classLoader); + Class pluginClass = Class.forName(cls, true, classLoader); + // TODO: Only create the plugin before version check if we need it for + // legacy version check. + T plugin = mInstanceFactory.create(pluginClass); + try { + VersionInfo version = checkVersion(pluginClass, plugin, mVersion); + if (DEBUG) Log.d(TAG, "createPlugin"); + return new PluginInfo<>(pkg, cls, plugin, pluginContext, version); + } catch (InvalidVersionException e) { + final int icon = Resources.getSystem().getIdentifier( + "stat_sys_warning", "drawable", "android"); + final int color = Resources.getSystem().getIdentifier( + "system_notification_accent_color", "color", "android"); + final Notification.Builder nb = new Notification.Builder(mContext, + PluginManager.NOTIFICATION_CHANNEL_ID) + .setStyle(new Notification.BigTextStyle()) + .setSmallIcon(icon) + .setWhen(0) + .setShowWhen(false) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setColor(mContext.getColor(color)); + String label = cls; + try { + label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString(); + } catch (NameNotFoundException e2) { + } + if (!e.isTooNew()) { + // Localization not required as this will never ever appear in a user build. + nb.setContentTitle("Plugin \"" + label + "\" is too old") + .setContentText("Contact plugin developer to get an updated" + + " version.\n" + e.getMessage()); + } else { + // Localization not required as this will never ever appear in a user build. + nb.setContentTitle("Plugin \"" + label + "\" is too new") + .setContentText("Check to see if an OTA is available.\n" + + e.getMessage()); + } + Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData( + Uri.parse("package://" + component.flattenToString())); + PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, + PendingIntent.FLAG_IMMUTABLE); + nb.addAction(new Action.Builder(null, "Disable plugin", pi).build()); + mNotificationManager.notify(SystemMessage.NOTE_PLUGIN, nb.build()); + // TODO: Warn user. + Log.w(TAG, "Plugin has invalid interface version " + plugin.getVersion() + + ", expected " + mVersion); + return null; + } + } catch (Throwable e) { + Log.w(TAG, "Couldn't load plugin: " + pkg, e); + return null; + } + } + + private VersionInfo checkVersion(Class pluginClass, T plugin, VersionInfo version) + throws InvalidVersionException { + VersionInfo pv = new VersionInfo().addClass(pluginClass); + if (pv.hasVersionInfo()) { + version.checkVersion(pv); + } else { + int fallbackVersion = plugin.getVersion(); + if (fallbackVersion != version.getDefaultVersion()) { + throw new InvalidVersionException("Invalid legacy version", false); + } + return null; + } + return pv; + } + + /** Returns class loader specific for the given plugin. */ + public ClassLoader getClassLoader(ApplicationInfo appInfo) { + if (!mIsDebuggable && !isPluginPackagePrivileged(appInfo.packageName)) { + Log.w(TAG, "Cannot get class loader for non-privileged plugin. Src:" + + appInfo.sourceDir + ", pkg: " + appInfo.packageName); + return null; + } + if (mClassLoaders.containsKey(appInfo.packageName)) { + return mClassLoaders.get(appInfo.packageName); + } + + List zipPaths = new ArrayList<>(); + List libPaths = new ArrayList<>(); + LoadedApk.makePaths(null, true, appInfo, zipPaths, libPaths); + ClassLoader classLoader = new PathClassLoader( + TextUtils.join(File.pathSeparator, zipPaths), + TextUtils.join(File.pathSeparator, libPaths), + getParentClassLoader()); + mClassLoaders.put(appInfo.packageName, classLoader); + return classLoader; + } + + private ClassLoader getParentClassLoader() { + if (mParentClassLoader == null) { + // Lazily load this so it doesn't have any effect on devices without plugins. + mParentClassLoader = new PluginManagerImpl.ClassLoaderFilter( + getClass().getClassLoader(), "com.android.systemui.plugin"); + } + return mParentClassLoader; + } + + /** + * Construct a {@link PluginInstanceManager} + */ + public static class Factory { + private final Context mContext; + private final PackageManager mPackageManager; + private final Executor mMainExecutor; + private final Executor mBgExecutor; + private final PluginInitializer mInitializer; + private final NotificationManager mNotificationManager; + private final PluginEnabler mPluginEnabler; + private final List mPrivilegedPlugins; + private InstanceFactory mInstanceFactory; + + public Factory(Context context, PackageManager packageManager, + Executor mainExecutor, Executor bgExecutor, PluginInitializer initializer, + NotificationManager notificationManager, PluginEnabler pluginEnabler, + List privilegedPlugins) { + mContext = context; + mPackageManager = packageManager; + mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; + mInitializer = initializer; + mNotificationManager = notificationManager; + mPluginEnabler = pluginEnabler; + mPrivilegedPlugins = privilegedPlugins; + + mInstanceFactory = new InstanceFactory<>(); + } + + @VisibleForTesting + Factory setInstanceFactory(InstanceFactory instanceFactory) { + mInstanceFactory = instanceFactory; + return this; + } + + PluginInstanceManager create( + String action, PluginListener listener, boolean allowMultiple, + VersionInfo version, boolean debuggable) { + return new PluginInstanceManager(mContext, mPackageManager, action, listener, + allowMultiple, mMainExecutor, mBgExecutor, version, debuggable, + mInitializer, mNotificationManager, mPluginEnabler, + mPrivilegedPlugins, (InstanceFactory) mInstanceFactory); + } + } + + public static class PluginContextWrapper extends ContextWrapper { + private final ClassLoader mClassLoader; + private LayoutInflater mInflater; + + public PluginContextWrapper(Context base, ClassLoader classLoader) { + super(base); + mClassLoader = classLoader; + } + + @Override + public ClassLoader getClassLoader() { + return mClassLoader; + } + + @Override + public Object getSystemService(String name) { + if (LAYOUT_INFLATER_SERVICE.equals(name)) { + if (mInflater == null) { + mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this); + } + return mInflater; + } + return getBaseContext().getSystemService(name); + } + } + + static class PluginInfo { + private final Context mPluginContext; + private final VersionInfo mVersion; + private final String mClass; + T mPlugin; + String mPackage; + + public PluginInfo(String pkg, String cls, T plugin, Context pluginContext, + VersionInfo info) { + mPlugin = plugin; + mClass = cls; + mPackage = pkg; + mPluginContext = pluginContext; + mVersion = info; + } + } + + static class InstanceFactory { + T create(Class cls) throws IllegalAccessException, InstantiationException { + return (T) cls.newInstance(); + } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java index c89be869115b5..d264bf2fae52c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java @@ -30,15 +30,13 @@ public interface PluginManager { /** Returns plugins that don't get disabled when an exceptoin occurs. */ String[] getPrivilegedPlugins(); - /** */ - void addPluginListener(PluginListener listener, Class cls); - /** */ - void addPluginListener(PluginListener listener, Class cls, + void addPluginListener(PluginListener listener, Class cls); + void addPluginListener(PluginListener listener, Class cls, boolean allowMultiple); void addPluginListener(String action, PluginListener listener, - Class cls); + Class cls); void addPluginListener(String action, PluginListener listener, - Class cls, boolean allowMultiple); + Class cls, boolean allowMultiple); void removePluginListener(PluginListener listener); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java index 7539f995dab47..ea7b0c34136a9 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java @@ -47,26 +47,26 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage private static final String TAG = PluginManagerImpl.class.getSimpleName(); static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN"; - private final ArrayMap, PluginActionManager> mPluginMap + private final ArrayMap, PluginInstanceManager> mPluginMap = new ArrayMap<>(); private final Map mClassLoaders = new ArrayMap<>(); private final ArraySet mPrivilegedPlugins = new ArraySet<>(); private final Context mContext; - private final PluginActionManager.Factory mActionManagerFactory; + private final PluginInstanceManager.Factory mInstanceManagerFactory; private final boolean mIsDebuggable; private final PluginPrefs mPluginPrefs; private final PluginEnabler mPluginEnabler; private boolean mListening; public PluginManagerImpl(Context context, - PluginActionManager.Factory actionManagerFactory, + PluginInstanceManager.Factory instanceManagerFactory, boolean debuggable, Optional defaultHandlerOptional, PluginEnabler pluginEnabler, PluginPrefs pluginPrefs, List privilegedPlugins) { mContext = context; - mActionManagerFactory = actionManagerFactory; + mInstanceManagerFactory = instanceManagerFactory; mIsDebuggable = debuggable; mPrivilegedPlugins.addAll(privilegedPlugins); mPluginPrefs = pluginPrefs; @@ -85,27 +85,25 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage return mPrivilegedPlugins.toArray(new String[0]); } - /** */ - public void addPluginListener(PluginListener listener, Class cls) { + public void addPluginListener(PluginListener listener, Class cls) { addPluginListener(listener, cls, false); } - /** */ - public void addPluginListener(PluginListener listener, Class cls, + public void addPluginListener(PluginListener listener, Class cls, boolean allowMultiple) { addPluginListener(PluginManager.Helper.getAction(cls), listener, cls, allowMultiple); } public void addPluginListener(String action, PluginListener listener, - Class cls) { + Class cls) { addPluginListener(action, listener, cls, false); } public void addPluginListener(String action, PluginListener listener, - Class cls, boolean allowMultiple) { + Class cls, boolean allowMultiple) { mPluginPrefs.addAction(action); - PluginActionManager p = mActionManagerFactory.create(action, listener, cls, - allowMultiple, isDebuggable()); + PluginInstanceManager p = mInstanceManagerFactory.create(action, listener, allowMultiple, + new VersionInfo().addClass(cls), isDebuggable()); p.loadAll(); synchronized (this) { mPluginMap.put(listener, p); @@ -137,7 +135,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage filter.addAction(PLUGIN_CHANGED); filter.addAction(DISABLE_PLUGIN); filter.addDataScheme("package"); - mContext.registerReceiver(this, filter, PluginActionManager.PLUGIN_PERMISSION, null); + mContext.registerReceiver(this, filter, PluginInstanceManager.PLUGIN_PERMISSION, null); filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); mContext.registerReceiver(this, filter); } @@ -152,7 +150,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage public void onReceive(Context context, Intent intent) { if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { synchronized (this) { - for (PluginActionManager manager : mPluginMap.values()) { + for (PluginInstanceManager manager : mPluginMap.values()) { manager.loadAll(); } } @@ -191,14 +189,12 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage } } synchronized (this) { - if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction()) - || Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) - || Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) { - for (PluginActionManager actionManager : mPluginMap.values()) { - actionManager.reloadPackage(pkg); + if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { + for (PluginInstanceManager manager : mPluginMap.values()) { + manager.onPackageChange(pkg); } } else { - for (PluginActionManager manager : mPluginMap.values()) { + for (PluginInstanceManager manager : mPluginMap.values()) { manager.onPackageRemoved(pkg); } } @@ -288,7 +284,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage // disable all the plugins, so we can be sure that SysUI is running as // best as possible. synchronized (this) { - for (PluginActionManager manager : mPluginMap.values()) { + for (PluginInstanceManager manager : mPluginMap.values()) { disabledAny |= manager.disableAll(); } } @@ -308,7 +304,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage boolean disabledAny = false; synchronized (this) { for (StackTraceElement element : throwable.getStackTrace()) { - for (PluginActionManager manager : mPluginMap.values()) { + for (PluginInstanceManager manager : mPluginMap.values()) { disabledAny |= manager.checkAndDisable(element.getClassName()); } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/VersionInfo.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/VersionInfo.java index 6be3243879d6d..bb845cd879238 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/VersionInfo.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/VersionInfo.java @@ -119,8 +119,6 @@ public class VersionInfo { public static class InvalidVersionException extends RuntimeException { private final boolean mTooNew; - private int mExpected; - private int mActual; public InvalidVersionException(String str, boolean tooNew) { super(str); @@ -130,21 +128,11 @@ public class VersionInfo { public InvalidVersionException(Class cls, boolean tooNew, int expected, int actual) { super(cls.getSimpleName() + " expected version " + expected + " but had " + actual); mTooNew = tooNew; - mExpected = expected; - mActual = actual; } public boolean isTooNew() { return mTooNew; } - - public int getExpectedVersion() { - return mExpected; - } - - public int getActualVersion() { - return mActual; - } } private static class Version { diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java index c7f1006a40423..f9617cad848e8 100644 --- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java +++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java @@ -53,7 +53,7 @@ public class PluginInflateContainer extends AutoReinflateContainer private static final String TAG = "PluginInflateContainer"; - private Class mClass; + private Class mClass; private View mPluginView; public PluginInflateContainer(Context context, @Nullable AttributeSet attrs) { @@ -61,7 +61,7 @@ public class PluginInflateContainer extends AutoReinflateContainer TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PluginInflateContainer); String viewType = a.getString(R.styleable.PluginInflateContainer_viewType); try { - mClass = (Class) Class.forName(viewType); + mClass = Class.forName(viewType); } catch (Exception e) { Log.d(TAG, "Problem getting class info " + viewType, e); mClass = null; diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java index 6a93fb7e4861a..1ea9b3c08b1ec 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java @@ -23,12 +23,10 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; -import com.android.systemui.dagger.PluginModule; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.shared.plugins.PluginActionManager; import com.android.systemui.shared.plugins.PluginEnabler; import com.android.systemui.shared.plugins.PluginInitializer; -import com.android.systemui.shared.plugins.PluginInstance; +import com.android.systemui.shared.plugins.PluginInstanceManager; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.plugins.PluginManagerImpl; import com.android.systemui.shared.plugins.PluginPrefs; @@ -73,28 +71,14 @@ public abstract class PluginsModule { @Provides @Singleton - static PluginInstance.Factory providesPluginInstanceFactory( - @Named(PLUGIN_PRIVILEGED) List privilegedPlugins, - @Named(PLUGIN_DEBUG) boolean isDebug) { - return new PluginInstance.Factory( - PluginModule.class.getClassLoader(), - new PluginInstance.InstanceFactory<>(), - new PluginInstance.VersionChecker(), - privilegedPlugins, - isDebug); - } - - @Provides - @Singleton - static PluginActionManager.Factory providePluginInstanceManagerFactory(Context context, + static PluginInstanceManager.Factory providePluginInstanceManagerFactory(Context context, PackageManager packageManager, @Main Executor mainExecutor, @Named(PLUGIN_THREAD) Executor pluginExecutor, PluginInitializer initializer, NotificationManager notificationManager, PluginEnabler pluginEnabler, - @Named(PLUGIN_PRIVILEGED) List privilegedPlugins, - PluginInstance.Factory pluginInstanceFactory) { - return new PluginActionManager.Factory( + @Named(PLUGIN_PRIVILEGED) List privilegedPlugins) { + return new PluginInstanceManager.Factory( context, packageManager, mainExecutor, pluginExecutor, initializer, - notificationManager, pluginEnabler, privilegedPlugins, pluginInstanceFactory); + notificationManager, pluginEnabler, privilegedPlugins); } @Provides @@ -107,7 +91,7 @@ public abstract class PluginsModule { @Provides static PluginManager providesPluginManager( Context context, - PluginActionManager.Factory instanceManagerFactory, + PluginInstanceManager.Factory instanceManagerFactory, @Named(PLUGIN_DEBUG) boolean debug, @Named(PRE_HANDLER) Optional uncaughtExceptionHandlerOptional, diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java index fe183fc9e872a..20857eaba7d48 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java @@ -38,8 +38,8 @@ import com.android.internal.util.ArrayUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.PluginEnablerImpl; -import com.android.systemui.shared.plugins.PluginActionManager; import com.android.systemui.shared.plugins.PluginEnabler; +import com.android.systemui.shared.plugins.PluginInstanceManager; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.plugins.PluginPrefs; @@ -102,7 +102,7 @@ public class PluginFragment extends PreferenceFragment { } List apps = pm.getPackagesHoldingPermissions(new String[]{ - PluginActionManager.PLUGIN_PERMISSION}, + PluginInstanceManager.PLUGIN_PERMISSION}, PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES); apps.forEach(app -> { if (!plugins.containsKey(app.packageName)) return; diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java similarity index 65% rename from packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java rename to packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java index ce366657b7b24..790b4dd11825b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java @@ -1,17 +1,15 @@ /* * 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 + * 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. + * 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.shared.plugins; @@ -21,6 +19,8 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -41,11 +41,13 @@ import android.test.suitebuilder.annotation.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestableContext; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.annotations.Requires; +import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -62,14 +64,14 @@ import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) -public class PluginActionManagerTest extends SysuiTestCase { +public class PluginInstanceManagerTest extends SysuiTestCase { private static final String PRIVILEGED_PACKAGE = "com.android.systemui.shared.plugins"; private TestPlugin mMockPlugin; private PackageManager mMockPm; - private PluginListener mMockListener; - private PluginActionManager mPluginActionManager; + private PluginListener mMockListener; + private PluginInstanceManager mPluginInstanceManager; private VersionInfo mMockVersionInfo; private PluginEnabler mMockEnabler; ComponentName mTestPluginComponentName = @@ -77,20 +79,15 @@ public class PluginActionManagerTest extends SysuiTestCase { private PluginInitializer mInitializer; private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); NotificationManager mNotificationManager; - private PluginInstance mPluginInstance; - private PluginInstance.Factory mPluginInstanceFactory = new PluginInstance.Factory( - this.getClass().getClassLoader(), - new PluginInstance.InstanceFactory<>(), new PluginInstance.VersionChecker(), - Collections.emptyList(), false) { + private PluginInstanceManager.Factory mInstanceManagerFactory; + private final PluginInstanceManager.InstanceFactory mPluginInstanceFactory = + new PluginInstanceManager.InstanceFactory() { @Override - public PluginInstance create(Context context, ApplicationInfo appInfo, - ComponentName componentName, Class pluginClass) { - return (PluginInstance) mPluginInstance; + Plugin create(Class cls) { + return mMockPlugin; } }; - private PluginActionManager.Factory mActionManagerFactory; - @Before public void setup() throws Exception { mContext = new MyContextWrapper(mContext); @@ -101,15 +98,13 @@ public class PluginActionManagerTest extends SysuiTestCase { mInitializer = mock(PluginInitializer.class); mNotificationManager = mock(NotificationManager.class); mMockPlugin = mock(TestPlugin.class); - mPluginInstance = mock(PluginInstance.class); - when(mPluginInstance.getComponentName()).thenReturn(mTestPluginComponentName); - when(mPluginInstance.getPackage()).thenReturn(mTestPluginComponentName.getPackageName()); - mActionManagerFactory = new PluginActionManager.Factory(getContext(), mMockPm, + mInstanceManagerFactory = new PluginInstanceManager.Factory(getContext(), mMockPm, mFakeExecutor, mFakeExecutor, mInitializer, mNotificationManager, mMockEnabler, - new ArrayList<>(), mPluginInstanceFactory); + new ArrayList<>()) + .setInstanceFactory(mPluginInstanceFactory); - mPluginActionManager = mActionManagerFactory.create("myAction", mMockListener, - TestPlugin.class, true, true); + mPluginInstanceManager = mInstanceManagerFactory.create("myAction", mMockListener, + true, mMockVersionInfo, true); when(mMockPlugin.getVersion()).thenReturn(1); } @@ -117,7 +112,7 @@ public class PluginActionManagerTest extends SysuiTestCase { public void testNoPlugins() { when(mMockPm.queryIntentServices(any(), anyInt())).thenReturn( Collections.emptyList()); - mPluginActionManager.loadAll(); + mPluginInstanceManager.loadAll(); mFakeExecutor.runAllReady(); @@ -126,47 +121,68 @@ public class PluginActionManagerTest extends SysuiTestCase { @Test public void testPluginCreate() throws Exception { - //Debug.waitForDebugger(); createPlugin(); // Verify startup lifecycle - verify(mPluginInstance).onCreate(mContext, mMockListener); + verify(mMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(), + ArgumentCaptor.forClass(Context.class).capture()); + verify(mMockListener).onPluginConnected(any(), any()); } @Test public void testPluginDestroy() throws Exception { createPlugin(); // Get into valid created state. - mPluginActionManager.destroy(); + mPluginInstanceManager.destroy(); mFakeExecutor.runAllReady(); + // Verify shutdown lifecycle - verify(mPluginInstance).onDestroy(mMockListener); + verify(mMockListener).onPluginDisconnected(ArgumentCaptor.forClass(Plugin.class).capture()); + verify(mMockPlugin).onDestroy(); + } + + @Test + public void testIncorrectVersion() throws Exception { + setupFakePmQuery(); + doThrow(new InvalidVersionException("", false)).when(mMockVersionInfo).checkVersion(any()); + + mPluginInstanceManager.loadAll(); + + mFakeExecutor.runAllReady(); + + // Plugin shouldn't be connected because it is the wrong version. + verify(mMockListener, never()).onPluginConnected(any(), any()); + verify(mNotificationManager).notify(eq(SystemMessage.NOTE_PLUGIN), any()); } @Test public void testReloadOnChange() throws Exception { createPlugin(); // Get into valid created state. - mPluginActionManager.reloadPackage(PRIVILEGED_PACKAGE); + mPluginInstanceManager.onPackageChange(PRIVILEGED_PACKAGE); mFakeExecutor.runAllReady(); // Verify the old one was destroyed. - verify(mPluginInstance).onDestroy(mMockListener); - verify(mPluginInstance, Mockito.times(2)) - .onCreate(mContext, mMockListener); + verify(mMockListener).onPluginDisconnected(ArgumentCaptor.forClass(Plugin.class).capture()); + verify(mMockPlugin).onDestroy(); + // Also verify we got a second onCreate. + verify(mMockPlugin, Mockito.times(2)).onCreate( + ArgumentCaptor.forClass(Context.class).capture(), + ArgumentCaptor.forClass(Context.class).capture()); + verify(mMockListener, Mockito.times(2)).onPluginConnected(any(), any()); } @Test public void testNonDebuggable() throws Exception { // Create a version that thinks the build is not debuggable. - mPluginActionManager = mActionManagerFactory.create("myAction", mMockListener, - TestPlugin.class, true, false); + mPluginInstanceManager = mInstanceManagerFactory.create("myAction", mMockListener, + true, mMockVersionInfo, false); setupFakePmQuery(); - mPluginActionManager.loadAll(); + mPluginInstanceManager.loadAll(); mFakeExecutor.runAllReady(); @@ -177,20 +193,22 @@ public class PluginActionManagerTest extends SysuiTestCase { @Test public void testNonDebuggable_privileged() throws Exception { // Create a version that thinks the build is not debuggable. - PluginActionManager.Factory factory = new PluginActionManager.Factory(getContext(), + PluginInstanceManager.Factory factory = new PluginInstanceManager.Factory(getContext(), mMockPm, mFakeExecutor, mFakeExecutor, mInitializer, mNotificationManager, - mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE), - mPluginInstanceFactory); - mPluginActionManager = factory.create("myAction", mMockListener, - TestPlugin.class, true, false); + mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE)); + factory.setInstanceFactory(mPluginInstanceFactory); + mPluginInstanceManager = factory.create("myAction", mMockListener, + true, mMockVersionInfo, false); setupFakePmQuery(); - mPluginActionManager.loadAll(); + mPluginInstanceManager.loadAll(); mFakeExecutor.runAllReady(); // Verify startup lifecycle - verify(mPluginInstance).onCreate(mContext, mMockListener); + verify(mMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(), + ArgumentCaptor.forClass(Context.class).capture()); + verify(mMockListener).onPluginConnected(any(), any()); } @Test @@ -198,12 +216,12 @@ public class PluginActionManagerTest extends SysuiTestCase { createPlugin(); // Get into valid created state. // Start with an unrelated class. - boolean result = mPluginActionManager.checkAndDisable(Activity.class.getName()); + boolean result = mPluginInstanceManager.checkAndDisable(Activity.class.getName()); assertFalse(result); verify(mMockEnabler, never()).setDisabled(any(ComponentName.class), anyInt()); // Now hand it a real class and make sure it disables the plugin. - result = mPluginActionManager.checkAndDisable(TestPlugin.class.getName()); + result = mPluginInstanceManager.checkAndDisable(TestPlugin.class.getName()); assertTrue(result); verify(mMockEnabler).setDisabled( mTestPluginComponentName, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH); @@ -213,24 +231,24 @@ public class PluginActionManagerTest extends SysuiTestCase { public void testDisableAll() throws Exception { createPlugin(); // Get into valid created state. - mPluginActionManager.disableAll(); + mPluginInstanceManager.disableAll(); verify(mMockEnabler).setDisabled( mTestPluginComponentName, PluginEnabler.DISABLED_FROM_SYSTEM_CRASH); } @Test - public void testDisablePrivileged() throws Exception { - PluginActionManager.Factory factory = new PluginActionManager.Factory(getContext(), + public void testDisableWhitelisted() throws Exception { + PluginInstanceManager.Factory factory = new PluginInstanceManager.Factory(getContext(), mMockPm, mFakeExecutor, mFakeExecutor, mInitializer, mNotificationManager, - mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE), - mPluginInstanceFactory); - mPluginActionManager = factory.create("myAction", mMockListener, - TestPlugin.class, true, false); + mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE)); + factory.setInstanceFactory(mPluginInstanceFactory); + mPluginInstanceManager = factory.create("myAction", mMockListener, + true, mMockVersionInfo, false); createPlugin(); // Get into valid created state. - mPluginActionManager.disableAll(); + mPluginInstanceManager.disableAll(); verify(mMockPm, never()).setComponentEnabledSetting( ArgumentCaptor.forClass(ComponentName.class).capture(), @@ -264,14 +282,14 @@ public class PluginActionManagerTest extends SysuiTestCase { private void createPlugin() throws Exception { setupFakePmQuery(); - mPluginActionManager.loadAll(); + mPluginInstanceManager.loadAll(); mFakeExecutor.runAllReady(); } // Real context with no registering/unregistering of receivers. private static class MyContextWrapper extends SysuiTestableContext { - MyContextWrapper(Context base) { + public MyContextWrapper(Context base) { super(base); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java deleted file mode 100644 index bb9a1e971fd0f..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2021 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.shared.plugins; - -import static junit.framework.Assert.assertNotNull; - -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.test.suitebuilder.annotation.SmallTest; - -import androidx.test.runner.AndroidJUnit4; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.Plugin; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.annotations.ProvidesInterface; -import com.android.systemui.plugins.annotations.Requires; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Collections; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class PluginInstanceTest extends SysuiTestCase { - - private static final String PRIVILEGED_PACKAGE = "com.android.systemui.plugins"; - - @Mock - private TestPluginImpl mMockPlugin; - @Mock - private PluginListener mMockListener; - @Mock - private VersionInfo mVersionInfo; - ComponentName mTestPluginComponentName = - new ComponentName(PRIVILEGED_PACKAGE, TestPluginImpl.class.getName()); - private PluginInstance mPluginInstance; - private PluginInstance.Factory mPluginInstanceFactory; - - private ApplicationInfo mAppInfo; - private Context mPluginContext; - @Mock - private PluginInstance.VersionChecker mVersionChecker; - - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - mAppInfo = mContext.getApplicationInfo(); - mAppInfo.packageName = mTestPluginComponentName.getPackageName(); - when(mVersionChecker.checkVersion(any(), any(), any())).thenReturn(mVersionInfo); - - mPluginInstanceFactory = new PluginInstance.Factory( - this.getClass().getClassLoader(), - new PluginInstance.InstanceFactory() { - @Override - TestPlugin create(Class cls) { - return mMockPlugin; - } - }, - mVersionChecker, - Collections.singletonList(PRIVILEGED_PACKAGE), - false); - - mPluginInstance = mPluginInstanceFactory.create( - mContext, mAppInfo, mTestPluginComponentName, TestPlugin.class); - mPluginContext = mPluginInstance.getPluginContext(); - } - - @Test - public void testCorrectVersion() { - assertNotNull(mPluginInstance); - } - - @Test(expected = VersionInfo.InvalidVersionException.class) - public void testIncorrectVersion() throws Exception { - - ComponentName wrongVersionTestPluginComponentName = - new ComponentName(PRIVILEGED_PACKAGE, TestPlugin.class.getName()); - - when(mVersionChecker.checkVersion(any(), any(), any())).thenThrow( - new VersionInfo.InvalidVersionException("test", true)); - - mPluginInstanceFactory.create( - mContext, mAppInfo, wrongVersionTestPluginComponentName, TestPlugin.class); - } - - @Test - public void testOnCreate() { - mPluginInstance.onCreate(mContext, mMockListener); - verify(mMockPlugin).onCreate(mContext, mPluginContext); - verify(mMockListener).onPluginConnected(mMockPlugin, mPluginContext); - } - - @Test - public void testOnDestroy() { - mPluginInstance.onDestroy(mMockListener); - verify(mMockListener).onPluginDisconnected(mMockPlugin); - verify(mMockPlugin).onDestroy(); - } - - // This target class doesn't matter, it just needs to have a Requires to hit the flow where - // the mock version info is called. - @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION) - public interface TestPlugin extends Plugin { - int VERSION = 1; - String ACTION = "testAction"; - } - - @Requires(target = TestPlugin.class, version = TestPlugin.VERSION) - public static class TestPluginImpl implements TestPlugin { - @Override - public void onCreate(Context sysuiContext, Context pluginContext) { - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java index 1eadd522352e5..4590dd8295506 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java @@ -13,7 +13,6 @@ */ package com.android.systemui.shared.plugins; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -55,10 +54,10 @@ public class PluginManagerTest extends SysuiTestCase { private static final String PRIVILEGED_PACKAGE = "com.android.systemui"; - private PluginActionManager.Factory mMockFactory; - private PluginActionManager mMockPluginInstance; + private PluginInstanceManager.Factory mMockFactory; + private PluginInstanceManager mMockPluginInstance; private PluginManagerImpl mPluginManager; - private PluginListener mMockListener; + private PluginListener mMockListener; private PackageManager mMockPackageManager; private PluginEnabler mPluginEnabler; private PluginPrefs mPluginPrefs; @@ -71,11 +70,11 @@ public class PluginManagerTest extends SysuiTestCase { public void setup() throws Exception { mRealExceptionHandler = Thread.getUncaughtExceptionPreHandler(); mMockExceptionHandler = mock(UncaughtExceptionHandler.class); - mMockFactory = mock(PluginActionManager.Factory.class); - mMockPluginInstance = mock(PluginActionManager.class); + mMockFactory = mock(PluginInstanceManager.Factory.class); + mMockPluginInstance = mock(PluginInstanceManager.class); mPluginEnabler = mock(PluginEnabler.class); mPluginPrefs = mock(PluginPrefs.class); - when(mMockFactory.create(any(), any(), eq(TestPlugin.class), anyBoolean(), anyBoolean())) + when(mMockFactory.create(any(), any(), Mockito.anyBoolean(), any(), Mockito.anyBoolean())) .thenReturn(mMockPluginInstance); mMockPackageManager = mock(PackageManager.class); @@ -117,8 +116,8 @@ public class PluginManagerTest extends SysuiTestCase { applicationInfo.sourceDir = sourceDir; applicationInfo.packageName = PRIVILEGED_PACKAGE; mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); - verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(TestPlugin.class), - eq(false), eq(false)); + verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(false), + any(VersionInfo.class), eq(false)); verify(mMockPluginInstance).loadAll(); } @@ -139,8 +138,8 @@ public class PluginManagerTest extends SysuiTestCase { invalidApplicationInfo.sourceDir = sourceDir; invalidApplicationInfo.packageName = "com.android.invalidpackage"; mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); - verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(TestPlugin.class), - eq(false), eq(false)); + verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(false), + any(VersionInfo.class), eq(false)); verify(mMockPluginInstance).loadAll(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java index d245c727dcf81..8e1c0f7301e1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java @@ -30,24 +30,24 @@ public class FakePluginManager implements PluginManager { @Override public void addPluginListener(String action, PluginListener listener, - Class cls, boolean allowMultiple) { + Class cls, boolean allowMultiple) { mLeakChecker.addCallback(listener); } @Override - public void addPluginListener(PluginListener listener, Class cls) { + public void addPluginListener(PluginListener listener, Class cls) { mLeakChecker.addCallback(listener); } @Override - public void addPluginListener(PluginListener listener, Class cls, + public void addPluginListener(PluginListener listener, Class cls, boolean allowMultiple) { mLeakChecker.addCallback(listener); } @Override public void addPluginListener(String action, PluginListener listener, - Class cls) { + Class cls) { mLeakChecker.addCallback(listener); }