Merge "Add one-shot plugin support"
This commit is contained in:
committed by
Android (Google) Code Review
commit
97d9813245
@@ -221,6 +221,15 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".SysuiRestartReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.systemui.action.RESTART" />
|
||||
|
||||
<data android:scheme="package" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name=".ImageWallpaper"
|
||||
android:permission="android.permission.BIND_WALLPAPER"
|
||||
android:exported="true" />
|
||||
|
||||
@@ -87,6 +87,21 @@ public class PluginInstanceManager<T extends Plugin> {
|
||||
isDebuggable = debuggable;
|
||||
}
|
||||
|
||||
public PluginInfo<T> 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<T> 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<T extends Plugin> {
|
||||
}
|
||||
}
|
||||
|
||||
private static class PluginInfo<T> {
|
||||
static class PluginInfo<T> {
|
||||
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;
|
||||
|
||||
@@ -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<PluginListener<?>, PluginInstanceManager> mPluginMap
|
||||
= new ArrayMap<>();
|
||||
private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>();
|
||||
private final ArraySet<String> 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 extends Plugin> 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<T> p = mFactory.createPluginInstanceManager(mContext, action, null,
|
||||
false, mBackgroundThread.getLooper(), version, this);
|
||||
mPluginPrefs.addAction(action);
|
||||
PluginInfo<T> info = p.getPlugin();
|
||||
if (info != null) {
|
||||
mOneShotPackages.add(info.mPackage);
|
||||
mHasOneShot = true;
|
||||
startListening();
|
||||
return info.mPlugin;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T extends Plugin> void addPluginListener(String action, PluginListener<T> 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()) {
|
||||
|
||||
@@ -80,7 +80,6 @@ public class SystemUIApplication extends Application {
|
||||
ShortcutKeyDispatcher.class,
|
||||
VendorServices.class,
|
||||
LatencyTester.class,
|
||||
DozeFactory.Initializer.class,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,31 +34,10 @@ import com.android.systemui.statusbar.phone.DozeParameters;
|
||||
|
||||
public class DozeFactory {
|
||||
|
||||
private static DozeFactory sInstance;
|
||||
private final DozeProvider mDozePlugin;
|
||||
|
||||
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<DozeProvider>() {
|
||||
@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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user