From: Jason Monk Date: Wed, 4 Jan 2017 20:13:11 +0000 (-0500) Subject: Add one-shot plugin support X-Git-Tag: android-x86-8.1-r1~5584^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=59d86ed2a80364aa27541d8117ccf80551b45e20;p=android-x86%2Fframeworks-base.git Add one-shot plugin support Should have happened a while ago. Test: runtest systemui Change-Id: I0da4deb5c297e8030213810815a408364ec97e14 --- diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 5574753bee6c..19c34ae14cb6 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -221,6 +221,15 @@ + + + + + + + + diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java index 388c71ddc0b3..7b8eae241929 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java @@ -87,6 +87,21 @@ public class PluginInstanceManager { isDebuggable = debuggable; } + public PluginInfo getPlugin() { + if (Looper.myLooper() != Looper.getMainLooper()) { + throw new RuntimeException("Must be called from UI thread"); + } + mPluginHandler.handleQueryPlugins(null /* All packages */); + if (mPluginHandler.mPlugins.size() > 0) { + mMainHandler.removeMessages(MainHandler.PLUGIN_CONNECTED); + PluginInfo info = mPluginHandler.mPlugins.get(0); + PluginPrefs.setHasPlugins(mContext); + info.mPlugin.onCreate(mContext, info.mPluginContext); + return info; + } + return null; + } + public void loadAll() { if (DEBUG) Log.d(TAG, "startListening"); mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL); @@ -366,11 +381,11 @@ public class PluginInstanceManager { } } - private static class PluginInfo { + static class PluginInfo { private final Context mPluginContext; - private T mPlugin; private String mClass; - private String mPackage; + T mPlugin; + String mPackage; public PluginInfo(String pkg, String cls, T plugin, Context pluginContext) { mPlugin = plugin; diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java index 6096eaf46457..471454749c2f 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java @@ -14,7 +14,10 @@ package com.android.systemui.plugins; +import android.app.Notification; +import android.app.Notification.Action; import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -23,16 +26,20 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; import android.net.Uri; import android.os.Build; import android.os.HandlerThread; import android.os.Looper; import android.os.SystemProperties; +import android.os.UserHandle; import android.util.ArrayMap; +import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper; +import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; import dalvik.system.PathClassLoader; @@ -54,11 +61,14 @@ public class PluginManager extends BroadcastReceiver { private final ArrayMap, PluginInstanceManager> mPluginMap = new ArrayMap<>(); private final Map mClassLoaders = new ArrayMap<>(); + private final ArraySet mOneShotPackages = new ArraySet<>(); private final Context mContext; private final PluginInstanceManagerFactory mFactory; private final boolean isDebuggable; private final PluginPrefs mPluginPrefs; private ClassLoaderFilter mParentClassLoader; + private boolean mListening; + private boolean mHasOneShot; private PluginManager(Context context) { this(context, new PluginInstanceManagerFactory(), @@ -80,6 +90,27 @@ public class PluginManager extends BroadcastReceiver { Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler); } + public T getOneShotPlugin(String action, int version) { + if (!isDebuggable) { + // Never ever ever allow these on production builds, they are only for prototyping. + return null; + } + if (Looper.myLooper() != Looper.getMainLooper()) { + throw new RuntimeException("Must be called from UI thread"); + } + PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, null, + false, mBackgroundThread.getLooper(), version, this); + mPluginPrefs.addAction(action); + PluginInfo info = p.getPlugin(); + if (info != null) { + mOneShotPackages.add(info.mPackage); + mHasOneShot = true; + startListening(); + return info.mPlugin; + } + return null; + } + public void addPluginListener(String action, PluginListener listener, int version) { addPluginListener(action, listener, version, false); @@ -96,9 +127,7 @@ public class PluginManager extends BroadcastReceiver { allowMultiple, mBackgroundThread.getLooper(), version, this); p.loadAll(); mPluginMap.put(listener, p); - if (mPluginMap.size() == 1) { - startListening(); - } + startListening(); } public void removePluginListener(PluginListener listener) { @@ -108,12 +137,12 @@ public class PluginManager extends BroadcastReceiver { } if (!mPluginMap.containsKey(listener)) return; mPluginMap.remove(listener).destroy(); - if (mPluginMap.size() == 0) { - stopListening(); - } + stopListening(); } private void startListening() { + if (mListening) return; + mListening = true; IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); @@ -126,6 +155,9 @@ public class PluginManager extends BroadcastReceiver { } private void stopListening() { + // Never stop listening if a one-shot is present. + if (!mListening || mHasOneShot) return; + mListening = false; mContext.unregisterReceiver(this); } @@ -147,6 +179,34 @@ public class PluginManager extends BroadcastReceiver { } else { Uri data = intent.getData(); String pkg = data.getEncodedSchemeSpecificPart(); + if (mOneShotPackages.contains(pkg)) { + int icon = mContext.getResources().getIdentifier("tuner", "drawable", + mContext.getPackageName()); + int color = Resources.getSystem().getIdentifier( + "system_notification_accent_color", "color", "android"); + String label = pkg; + try { + PackageManager pm = mContext.getPackageManager(); + label = pm.getApplicationInfo(pkg, 0).loadLabel(pm).toString(); + } catch (NameNotFoundException e) { + } + // Localization not required as this will never ever appear in a user build. + final Notification.Builder nb = new Notification.Builder(mContext) + .setSmallIcon(icon) + .setWhen(0) + .setShowWhen(false) + .setPriority(Notification.PRIORITY_MAX) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setColor(mContext.getColor(color)) + .setContentTitle("Plugin \"" + label + "\" has updated") + .setContentText("Restart SysUI for changes to take effect."); + Intent i = new Intent("com.android.systemui.action.RESTART").setData( + Uri.parse("package://" + pkg)); + PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0); + nb.addAction(new Action.Builder(null, "Restart SysUI", pi).build()); + mContext.getSystemService(NotificationManager.class).notifyAsUser(pkg, + SystemMessage.NOTE_PLUGIN, nb.build(), UserHandle.ALL); + } clearClassLoader(pkg); if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { for (PluginInstanceManager manager : mPluginMap.values()) { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index d109ae197714..19880230186a 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -80,7 +80,6 @@ public class SystemUIApplication extends Application { ShortcutKeyDispatcher.class, VendorServices.class, LatencyTester.class, - DozeFactory.Initializer.class, }; /** diff --git a/packages/SystemUI/src/com/android/systemui/SysuiRestartReceiver.java b/packages/SystemUI/src/com/android/systemui/SysuiRestartReceiver.java new file mode 100644 index 000000000000..cdeef2fbfad1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/SysuiRestartReceiver.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 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; + +import android.app.NotificationManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Process; + +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; + +public class SysuiRestartReceiver extends BroadcastReceiver { + + public static String ACTION = "com.android.systemui.action.RESTART"; + + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION.equals(intent.getAction())) { + String pkg = intent.getData().toString().substring(10); + NotificationManager.from(context).cancel(pkg, SystemMessage.NOTE_PLUGIN); + Process.killProcess(Process.myPid()); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index 5b10756988a4..f0deaf06b46e 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -34,31 +34,10 @@ import com.android.systemui.statusbar.phone.DozeParameters; public class DozeFactory { - private static DozeFactory sInstance; - - private DozeProvider mDozePlugin; - - /** Returns the singleton instance. */ - public static DozeFactory getInstance(Context context) { - if (sInstance == null) { - sInstance = new DozeFactory(); - PluginManager.getInstance(context).addPluginListener(DozeProvider.ACTION, - new PluginListener() { - @Override - public void onPluginConnected(DozeProvider plugin) { - sInstance.mDozePlugin = plugin; - } - - @Override - public void onPluginDisconnected(DozeProvider plugin) { - if (sInstance.mDozePlugin == plugin) { - sInstance.mDozePlugin = null; - } - } - }, - DozeProvider.VERSION, false /* Only one */); - } - return sInstance; + private final DozeProvider mDozePlugin; + + public DozeFactory(DozeProvider plugin) { + mDozePlugin = plugin; } /** Creates a DozeMachine with its parts for {@code dozeService}. */ @@ -202,13 +181,4 @@ public class DozeFactory { return mInner.wrap(runnable); } } - - /** Hack: We need to initialize the plugin listener before doze actually starts. - * This will be unnecessary once we have proper one-shot support */ - public static class Initializer extends SystemUI { - @Override - public void start() { - getInstance(mContext); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java index 78b96b34efad..52f1fb4c3c65 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java @@ -19,6 +19,10 @@ package com.android.systemui.doze; import android.service.dreams.DreamService; import android.util.Log; +import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.PluginManager; +import com.android.systemui.plugins.doze.DozeProvider; + import java.io.FileDescriptor; import java.io.PrintWriter; @@ -38,7 +42,9 @@ public class DozeService extends DreamService implements DozeMachine.Service { setWindowless(true); - mDozeMachine = DozeFactory.getInstance(getApplication()).assembleMachine(this); + DozeProvider provider = PluginManager.getInstance(this) + .getOneShotPlugin(DozeProvider.ACTION, DozeProvider.VERSION); + mDozeMachine = new DozeFactory(provider).assembleMachine(this); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java index 01fd97c9a4a7..d529ee105dd9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java @@ -15,6 +15,7 @@ package com.android.systemui.plugins; import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.any; @@ -39,11 +40,13 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.HandlerThread; import android.os.UserHandle; +import android.support.test.annotation.UiThreadTest; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; import org.junit.After; import org.junit.Before; @@ -92,6 +95,15 @@ public class PluginInstanceManagerTest extends SysuiTestCase { sMockPlugin = null; } + @UiThreadTest + @Test + public void testGetPlugin() throws Exception { + setupFakePmQuery(); + PluginInfo p = mPluginInstanceManager.getPlugin(); + assertNotNull(p.mPlugin); + verify(sMockPlugin).onCreate(any(), any()); + } + @Test public void testNoPlugins() throws Exception { when(mMockPm.queryIntentServices(any(), anyInt())).thenReturn( diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java index d5ada678c885..a58407b2ae23 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java @@ -13,6 +13,8 @@ */ package com.android.systemui.plugins; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -24,11 +26,13 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; +import android.support.test.annotation.UiThreadTest; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; import com.android.systemui.plugins.PluginManager.PluginInstanceManagerFactory; import org.junit.Before; @@ -66,6 +70,16 @@ public class PluginManagerTest extends SysuiTestCase { mMockListener = mock(PluginListener.class); } + @UiThreadTest + @Test + public void testOneShot() { + Plugin mockPlugin = mock(Plugin.class); + when(mMockPluginInstance.getPlugin()).thenReturn(new PluginInfo(null, null, mockPlugin, + null)); + Plugin result = mPluginManager.getOneShotPlugin("myAction", 1); + assertTrue(result == mockPlugin); + } + @Test public void testAddListener() { mPluginManager.addPluginListener("myAction", mMockListener, 1); @@ -86,9 +100,12 @@ public class PluginManagerTest extends SysuiTestCase { mPluginManager = new PluginManager(getContext(), mMockFactory, false, mMockExceptionHandler); resetExceptionHandler(); - mPluginManager.addPluginListener("myAction", mMockListener, 1); + mPluginManager.addPluginListener("myAction", mMockListener, 1); verify(mMockPluginInstance, Mockito.never()).loadAll(); + + assertNull(mPluginManager.getOneShotPlugin("myPlugin", 1)); + verify(mMockPluginInstance, Mockito.never()).getPlugin(); } @Test