Should have happened a while ago.
Test: runtest systemui
Change-Id: I0da4deb5c297e8030213810815a408364ec97e14
</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" />
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);
}
}
- 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;
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;
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;
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(),
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);
allowMultiple, mBackgroundThread.getLooper(), version, this);
p.loadAll();
mPluginMap.put(listener, p);
- if (mPluginMap.size() == 1) {
- startListening();
- }
+ startListening();
}
public void removePluginListener(PluginListener<?> listener) {
}
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);
}
private void stopListening() {
+ // Never stop listening if a one-shot is present.
+ if (!mListening || mHasOneShot) return;
+ mListening = false;
mContext.unregisterReceiver(this);
}
} 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()) {
ShortcutKeyDispatcher.class,
VendorServices.class,
LatencyTester.class,
- DozeFactory.Initializer.class,
};
/**
--- /dev/null
+/*
+ * 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());
+ }
+ }
+}
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<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;
+ private final DozeProvider mDozePlugin;
+
+ public DozeFactory(DozeProvider plugin) {
+ mDozePlugin = plugin;
}
/** Creates a DozeMachine with its parts for {@code dozeService}. */
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);
- }
- }
}
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;
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
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;
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;
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(
*/
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;
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;
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);
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