Move out test utilities to a Testables library
Test: runtest --path frameworks/base/tests/testablets/tests
&& runtest systemui
Change-Id: Ideef4aef5f26136b1741c556b9be5884f38842a0
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index bb7e19d..4dfaf45 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -30,6 +30,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManagerImpl;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -73,6 +74,7 @@
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerServiceImpl;
import com.android.systemui.util.leak.GarbageMonitor;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.util.leak.LeakReporter;
@@ -200,7 +202,7 @@
new DeviceProvisionedControllerImpl(mContext));
mProviders.put(PluginManager.class, () ->
- new PluginManager(mContext));
+ new PluginManagerImpl(mContext));
mProviders.put(AssistManager.class, () ->
new AssistManager(getDependency(DeviceProvisionedController.class), mContext));
@@ -223,7 +225,7 @@
getDependency(LeakReporter.class)));
mProviders.put(TunerService.class, () ->
- new TunerService(mContext));
+ new TunerServiceImpl(mContext));
mProviders.put(StatusBarWindowManager.class, () ->
new StatusBarWindowManager(mContext));
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
index 79f78c9..07ac52d 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
@@ -62,10 +62,10 @@
final PluginHandler mPluginHandler;
private final boolean isDebuggable;
private final PackageManager mPm;
- private final PluginManager mManager;
+ private final PluginManagerImpl mManager;
PluginInstanceManager(Context context, String action, PluginListener<T> listener,
- boolean allowMultiple, Looper looper, VersionInfo version, PluginManager manager) {
+ boolean allowMultiple, Looper looper, VersionInfo version, PluginManagerImpl manager) {
this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version,
manager, Build.IS_DEBUGGABLE);
}
@@ -73,7 +73,7 @@
@VisibleForTesting
PluginInstanceManager(Context context, PackageManager pm, String action,
PluginListener<T> listener, boolean allowMultiple, Looper looper, VersionInfo version,
- PluginManager manager, boolean debuggable) {
+ PluginManagerImpl manager, boolean debuggable) {
mMainHandler = new MainHandler(Looper.getMainLooper());
mPluginHandler = new PluginHandler(looper);
mManager = manager;
@@ -346,7 +346,7 @@
.setContentText("Check to see if an OTA is available.\n"
+ e.getMessage());
}
- Intent i = new Intent(PluginManager.DISABLE_PLUGIN).setData(
+ Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData(
Uri.parse("package://" + component.flattenToString()));
PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0);
nb.addAction(new Action.Builder(null, "Disable plugin", pi).build());
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java
index 9ad862d..298eaf1 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
@@ -14,276 +14,33 @@
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.Intent;
-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.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.SystemProperties;
-import android.os.UserHandle;
import android.text.TextUtils;
-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.Dependency;
-import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper;
-import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
import com.android.systemui.plugins.annotations.ProvidesInterface;
-import dalvik.system.PathClassLoader;
+public interface PluginManager {
-import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.Map;
-
-/**
- * @see Plugin
- */
-public class PluginManager extends BroadcastReceiver {
-
- public static final String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED";
-
- static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN";
+ String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED";
// must be one of the channels created in NotificationChannels.java
- static final String NOTIFICATION_CHANNEL_ID = "ALR";
+ String NOTIFICATION_CHANNEL_ID = "ALR";
- private static PluginManager sInstance;
+ <T extends Plugin> T getOneShotPlugin(Class<T> cls);
+ <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls);
- private final HandlerThread mBackgroundThread;
- 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;
+ <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls);
+ <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
+ boolean allowMultiple);
+ <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+ Class<?> cls);
+ <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+ Class cls, boolean allowMultiple);
- public PluginManager(Context context) {
- this(context, new PluginInstanceManagerFactory(),
- Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler());
- }
+ void removePluginListener(PluginListener<?> listener);
- @VisibleForTesting
- PluginManager(Context context, PluginInstanceManagerFactory factory, boolean debuggable,
- UncaughtExceptionHandler defaultHandler) {
- mContext = context;
- mFactory = factory;
- mBackgroundThread = new HandlerThread("Plugins");
- mBackgroundThread.start();
- isDebuggable = debuggable;
- mPluginPrefs = new PluginPrefs(mContext);
+ <T> boolean dependsOn(Plugin p, Class<T> cls);
- PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler(
- defaultHandler);
- Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
- if (isDebuggable) {
- new Handler(mBackgroundThread.getLooper()).post(() -> {
- // Plugin dependencies that don't have another good home can go here, but
- // dependencies that have better places to init can happen elsewhere.
- Dependency.get(PluginDependencyProvider.class)
- .allowPluginDependency(ActivityStarter.class);
- });
- }
- }
-
- public <T extends Plugin> T getOneShotPlugin(Class<T> cls) {
- ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
- if (info == null) {
- throw new RuntimeException(cls + " doesn't provide an interface");
- }
- if (TextUtils.isEmpty(info.action())) {
- throw new RuntimeException(cls + " doesn't provide an action");
- }
- return getOneShotPlugin(info.action(), cls);
- }
-
- public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) {
- 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(), cls, 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(PluginListener<T> listener, Class<?> cls) {
- addPluginListener(listener, cls, false);
- }
-
- public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
- boolean allowMultiple) {
- addPluginListener(getAction(cls), listener, cls, allowMultiple);
- }
-
- public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
- Class<?> cls) {
- addPluginListener(action, listener, cls, false);
- }
-
- public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
- Class cls, boolean allowMultiple) {
- if (!isDebuggable) {
- // Never ever ever allow these on production builds, they are only for prototyping.
- return;
- }
- mPluginPrefs.addAction(action);
- PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
- allowMultiple, mBackgroundThread.getLooper(), cls, this);
- p.loadAll();
- mPluginMap.put(listener, p);
- startListening();
- }
-
- public void removePluginListener(PluginListener<?> listener) {
- if (!isDebuggable) {
- // Never ever ever allow these on production builds, they are only for prototyping.
- return;
- }
- if (!mPluginMap.containsKey(listener)) return;
- mPluginMap.remove(listener).destroy();
- 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);
- filter.addAction(PLUGIN_CHANGED);
- filter.addAction(DISABLE_PLUGIN);
- filter.addDataScheme("package");
- mContext.registerReceiver(this, filter);
- filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
- mContext.registerReceiver(this, filter);
- }
-
- private void stopListening() {
- // Never stop listening if a one-shot is present.
- if (!mListening || mHasOneShot) return;
- mListening = false;
- mContext.unregisterReceiver(this);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
- for (PluginInstanceManager manager : mPluginMap.values()) {
- manager.loadAll();
- }
- } else if (DISABLE_PLUGIN.equals(intent.getAction())) {
- Uri uri = intent.getData();
- ComponentName component = ComponentName.unflattenFromString(
- uri.toString().substring(10));
- mContext.getPackageManager().setComponentEnabledSetting(component,
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
- PackageManager.DONT_KILL_APP);
- mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(),
- SystemMessage.NOTE_PLUGIN);
- } 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, NOTIFICATION_CHANNEL_ID)
- .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()) {
- manager.onPackageChange(pkg);
- }
- } else {
- for (PluginInstanceManager manager : mPluginMap.values()) {
- manager.onPackageRemoved(pkg);
- }
- }
- }
- }
-
- public ClassLoader getClassLoader(String sourceDir, String pkg) {
- if (mClassLoaders.containsKey(pkg)) {
- return mClassLoaders.get(pkg);
- }
- ClassLoader classLoader = new PathClassLoader(sourceDir, getParentClassLoader());
- mClassLoaders.put(pkg, classLoader);
- return classLoader;
- }
-
- private void clearClassLoader(String pkg) {
- mClassLoaders.remove(pkg);
- }
-
- ClassLoader getParentClassLoader() {
- if (mParentClassLoader == null) {
- // Lazily load this so it doesn't have any effect on devices without plugins.
- mParentClassLoader = new ClassLoaderFilter(getClass().getClassLoader(),
- "com.android.systemui.plugin");
- }
- return mParentClassLoader;
- }
-
- public Context getContext(ApplicationInfo info, String pkg) throws NameNotFoundException {
- ClassLoader classLoader = getClassLoader(info.sourceDir, pkg);
- return new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader);
- }
-
- public static <P> String getAction(Class<P> cls) {
+ static <P> String getAction(Class<P> cls) {
ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
if (info == null) {
throw new RuntimeException(cls + " doesn't provide an interface");
@@ -293,82 +50,4 @@
}
return info.action();
}
-
- public <T> boolean dependsOn(Plugin p, Class<T> cls) {
- for (int i = 0; i < mPluginMap.size(); i++) {
- if (mPluginMap.valueAt(i).dependsOn(p, cls)) {
- return true;
- }
- }
- return false;
- }
-
- @VisibleForTesting
- public static class PluginInstanceManagerFactory {
- public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
- String action, PluginListener<T> listener, boolean allowMultiple, Looper looper,
- Class<?> cls, PluginManager manager) {
- return new PluginInstanceManager(context, action, listener, allowMultiple, looper,
- new VersionInfo().addClass(cls), manager);
- }
- }
-
- // This allows plugins to include any libraries or copied code they want by only including
- // classes from the plugin library.
- private static class ClassLoaderFilter extends ClassLoader {
- private final String mPackage;
- private final ClassLoader mBase;
-
- public ClassLoaderFilter(ClassLoader base, String pkg) {
- super(ClassLoader.getSystemClassLoader());
- mBase = base;
- mPackage = pkg;
- }
-
- @Override
- protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
- if (!name.startsWith(mPackage)) super.loadClass(name, resolve);
- return mBase.loadClass(name);
- }
- }
-
- private class PluginExceptionHandler implements UncaughtExceptionHandler {
- private final UncaughtExceptionHandler mHandler;
-
- private PluginExceptionHandler(UncaughtExceptionHandler handler) {
- mHandler = handler;
- }
-
- @Override
- public void uncaughtException(Thread thread, Throwable throwable) {
- if (SystemProperties.getBoolean("plugin.debugging", false)) {
- mHandler.uncaughtException(thread, throwable);
- return;
- }
- // Search for and disable plugins that may have been involved in this crash.
- boolean disabledAny = checkStack(throwable);
- if (!disabledAny) {
- // We couldn't find any plugins involved in this crash, just to be safe
- // disable all the plugins, so we can be sure that SysUI is running as
- // best as possible.
- for (PluginInstanceManager manager : mPluginMap.values()) {
- manager.disableAll();
- }
- }
-
- // Run the normal exception handler so we can crash and cleanup our state.
- mHandler.uncaughtException(thread, throwable);
- }
-
- private boolean checkStack(Throwable throwable) {
- if (throwable == null) return false;
- boolean disabledAny = false;
- for (StackTraceElement element : throwable.getStackTrace()) {
- for (PluginInstanceManager manager : mPluginMap.values()) {
- disabledAny |= manager.checkAndDisable(element.getClassName());
- }
- }
- return disabledAny | checkStack(throwable.getCause());
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
new file mode 100644
index 0000000..1fb6c87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
@@ -0,0 +1,358 @@
+/*
+ * 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.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.Intent;
+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.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.text.TextUtils;
+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.Dependency;
+import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper;
+import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+import dalvik.system.PathClassLoader;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.Map;
+
+/**
+ * @see Plugin
+ */
+public class PluginManagerImpl extends BroadcastReceiver implements PluginManager {
+
+ static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN";
+
+ private static PluginManager sInstance;
+
+ private final HandlerThread mBackgroundThread;
+ 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;
+
+ public PluginManagerImpl(Context context) {
+ this(context, new PluginInstanceManagerFactory(),
+ Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler());
+ }
+
+ @VisibleForTesting
+ PluginManagerImpl(Context context, PluginInstanceManagerFactory factory, boolean debuggable,
+ UncaughtExceptionHandler defaultHandler) {
+ mContext = context;
+ mFactory = factory;
+ mBackgroundThread = new HandlerThread("Plugins");
+ mBackgroundThread.start();
+ isDebuggable = debuggable;
+ mPluginPrefs = new PluginPrefs(mContext);
+
+ PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler(
+ defaultHandler);
+ Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
+ if (isDebuggable) {
+ new Handler(mBackgroundThread.getLooper()).post(() -> {
+ // Plugin dependencies that don't have another good home can go here, but
+ // dependencies that have better places to init can happen elsewhere.
+ Dependency.get(PluginDependencyProvider.class)
+ .allowPluginDependency(ActivityStarter.class);
+ });
+ }
+ }
+
+ public <T extends Plugin> T getOneShotPlugin(Class<T> cls) {
+ ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
+ if (info == null) {
+ throw new RuntimeException(cls + " doesn't provide an interface");
+ }
+ if (TextUtils.isEmpty(info.action())) {
+ throw new RuntimeException(cls + " doesn't provide an action");
+ }
+ return getOneShotPlugin(info.action(), cls);
+ }
+
+ public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) {
+ 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(), cls, 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(PluginListener<T> listener, Class<?> cls) {
+ addPluginListener(listener, cls, false);
+ }
+
+ public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
+ boolean allowMultiple) {
+ addPluginListener(PluginManager.getAction(cls), listener, cls, allowMultiple);
+ }
+
+ public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+ Class<?> cls) {
+ addPluginListener(action, listener, cls, false);
+ }
+
+ public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+ Class cls, boolean allowMultiple) {
+ if (!isDebuggable) {
+ // Never ever ever allow these on production builds, they are only for prototyping.
+ return;
+ }
+ mPluginPrefs.addAction(action);
+ PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
+ allowMultiple, mBackgroundThread.getLooper(), cls, this);
+ p.loadAll();
+ mPluginMap.put(listener, p);
+ startListening();
+ }
+
+ public void removePluginListener(PluginListener<?> listener) {
+ if (!isDebuggable) {
+ // Never ever ever allow these on production builds, they are only for prototyping.
+ return;
+ }
+ if (!mPluginMap.containsKey(listener)) return;
+ mPluginMap.remove(listener).destroy();
+ 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);
+ filter.addAction(PLUGIN_CHANGED);
+ filter.addAction(DISABLE_PLUGIN);
+ filter.addDataScheme("package");
+ mContext.registerReceiver(this, filter);
+ filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
+ mContext.registerReceiver(this, filter);
+ }
+
+ private void stopListening() {
+ // Never stop listening if a one-shot is present.
+ if (!mListening || mHasOneShot) return;
+ mListening = false;
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+ for (PluginInstanceManager manager : mPluginMap.values()) {
+ manager.loadAll();
+ }
+ } else if (DISABLE_PLUGIN.equals(intent.getAction())) {
+ Uri uri = intent.getData();
+ ComponentName component = ComponentName.unflattenFromString(
+ uri.toString().substring(10));
+ mContext.getPackageManager().setComponentEnabledSetting(component,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(),
+ SystemMessage.NOTE_PLUGIN);
+ } 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, NOTIFICATION_CHANNEL_ID)
+ .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()) {
+ manager.onPackageChange(pkg);
+ }
+ } else {
+ for (PluginInstanceManager manager : mPluginMap.values()) {
+ manager.onPackageRemoved(pkg);
+ }
+ }
+ }
+ }
+
+ public ClassLoader getClassLoader(String sourceDir, String pkg) {
+ if (mClassLoaders.containsKey(pkg)) {
+ return mClassLoaders.get(pkg);
+ }
+ ClassLoader classLoader = new PathClassLoader(sourceDir, getParentClassLoader());
+ mClassLoaders.put(pkg, classLoader);
+ return classLoader;
+ }
+
+ private void clearClassLoader(String pkg) {
+ mClassLoaders.remove(pkg);
+ }
+
+ ClassLoader getParentClassLoader() {
+ if (mParentClassLoader == null) {
+ // Lazily load this so it doesn't have any effect on devices without plugins.
+ mParentClassLoader = new ClassLoaderFilter(getClass().getClassLoader(),
+ "com.android.systemui.plugin");
+ }
+ return mParentClassLoader;
+ }
+
+ public Context getContext(ApplicationInfo info, String pkg) throws NameNotFoundException {
+ ClassLoader classLoader = getClassLoader(info.sourceDir, pkg);
+ return new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader);
+ }
+
+ public <T> boolean dependsOn(Plugin p, Class<T> cls) {
+ for (int i = 0; i < mPluginMap.size(); i++) {
+ if (mPluginMap.valueAt(i).dependsOn(p, cls)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ public static class PluginInstanceManagerFactory {
+ public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
+ String action, PluginListener<T> listener, boolean allowMultiple, Looper looper,
+ Class<?> cls, PluginManagerImpl manager) {
+ return new PluginInstanceManager(context, action, listener, allowMultiple, looper,
+ new VersionInfo().addClass(cls), manager);
+ }
+ }
+
+ // This allows plugins to include any libraries or copied code they want by only including
+ // classes from the plugin library.
+ private static class ClassLoaderFilter extends ClassLoader {
+ private final String mPackage;
+ private final ClassLoader mBase;
+
+ public ClassLoaderFilter(ClassLoader base, String pkg) {
+ super(ClassLoader.getSystemClassLoader());
+ mBase = base;
+ mPackage = pkg;
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ if (!name.startsWith(mPackage)) super.loadClass(name, resolve);
+ return mBase.loadClass(name);
+ }
+ }
+
+ private class PluginExceptionHandler implements UncaughtExceptionHandler {
+ private final UncaughtExceptionHandler mHandler;
+
+ private PluginExceptionHandler(UncaughtExceptionHandler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public void uncaughtException(Thread thread, Throwable throwable) {
+ if (SystemProperties.getBoolean("plugin.debugging", false)) {
+ mHandler.uncaughtException(thread, throwable);
+ return;
+ }
+ // Search for and disable plugins that may have been involved in this crash.
+ boolean disabledAny = checkStack(throwable);
+ if (!disabledAny) {
+ // We couldn't find any plugins involved in this crash, just to be safe
+ // disable all the plugins, so we can be sure that SysUI is running as
+ // best as possible.
+ for (PluginInstanceManager manager : mPluginMap.values()) {
+ manager.disableAll();
+ }
+ }
+
+ // Run the normal exception handler so we can crash and cleanup our state.
+ mHandler.uncaughtException(thread, throwable);
+ }
+
+ private boolean checkStack(Throwable throwable) {
+ if (throwable == null) return false;
+ boolean disabledAny = false;
+ for (StackTraceElement element : throwable.getStackTrace()) {
+ for (PluginInstanceManager manager : mPluginMap.values()) {
+ disabledAny |= manager.checkAndDisable(element.getClassName());
+ }
+ }
+ return disabledAny | checkStack(throwable.getCause());
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 7c4f2ee..369ce69 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -1,226 +1,94 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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
+ * 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.tuner;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
import android.os.UserHandle;
-import android.os.UserManager;
import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
import com.android.systemui.DemoMode;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.SysUiServiceProvider;
-import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIApplication;
-import com.android.systemui.settings.CurrentUserTracker;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.util.leak.LeakDetector;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
-
-
-public class TunerService {
+public abstract class TunerService {
public static final String ACTION_CLEAR = "com.android.systemui.action.CLEAR_TUNER";
- private static final String TUNER_VERSION = "sysui_tuner_version";
+ public abstract void clearAll();
+ public abstract void destroy();
- private static final int CURRENT_TUNER_VERSION = 1;
+ public abstract String getValue(String setting);
+ public abstract int getValue(String setting, int def);
+ public abstract String getValue(String setting, String def);
- private final Observer mObserver = new Observer();
- // Map of Uris we listen on to their settings keys.
- private final ArrayMap<Uri, String> mListeningUris = new ArrayMap<>();
- // Map of settings keys to the listener.
- private final HashMap<String, Set<Tunable>> mTunableLookup = new HashMap<>();
- // Set of all tunables, used for leak detection.
- private final HashSet<Tunable> mTunables = LeakDetector.ENABLED ? new HashSet<>() : null;
- private final Context mContext;
+ public abstract void setValue(String setting, String value);
+ public abstract void setValue(String setting, int value);
- private ContentResolver mContentResolver;
- private int mCurrentUser;
- private CurrentUserTracker mUserTracker;
+ public abstract void addTunable(Tunable tunable, String... keys);
+ public abstract void removeTunable(Tunable tunable);
- public TunerService(Context context) {
- mContext = context;
- mContentResolver = mContext.getContentResolver();
+ public interface Tunable {
+ void onTuningChanged(String key, String newValue);
+ }
- for (UserInfo user : UserManager.get(mContext).getUsers()) {
- mCurrentUser = user.getUserHandle().getIdentifier();
- if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) {
- upgradeTuner(getValue(TUNER_VERSION, 0), CURRENT_TUNER_VERSION);
+ private static Context userContext(Context context) {
+ try {
+ return context.createPackageContextAsUser(context.getPackageName(), 0,
+ new UserHandle(ActivityManager.getCurrentUser()));
+ } catch (NameNotFoundException e) {
+ return context;
+ }
+ }
+
+ public static final void setTunerEnabled(Context context, boolean enabled) {
+ userContext(context).getPackageManager().setComponentEnabledSetting(
+ new ComponentName(context, TunerActivity.class),
+ enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+
+ userContext(context).getPackageManager().setComponentEnabledSetting(
+ new ComponentName(context, TunerActivity.ACTIVITY_ALIAS_NAME),
+ enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ public static final boolean isTunerEnabled(Context context) {
+ return userContext(context).getPackageManager().getComponentEnabledSetting(
+ new ComponentName(context, TunerActivity.class))
+ == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ }
+
+ public static class ClearReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_CLEAR.equals(intent.getAction())) {
+ Dependency.get(TunerService.class).clearAll();
}
}
-
- mCurrentUser = ActivityManager.getCurrentUser();
- mUserTracker = new CurrentUserTracker(mContext) {
- @Override
- public void onUserSwitched(int newUserId) {
- mCurrentUser = newUserId;
- reloadAll();
- reregisterAll();
- }
- };
- mUserTracker.startTracking();
- }
-
- public void destroy() {
- mUserTracker.stopTracking();
- }
-
- private void upgradeTuner(int oldVersion, int newVersion) {
- if (oldVersion < 1) {
- String blacklistStr = getValue(StatusBarIconController.ICON_BLACKLIST);
- if (blacklistStr != null) {
- ArraySet<String> iconBlacklist =
- StatusBarIconController.getIconBlacklist(blacklistStr);
-
- iconBlacklist.add("rotate");
- iconBlacklist.add("headset");
-
- Settings.Secure.putStringForUser(mContentResolver,
- StatusBarIconController.ICON_BLACKLIST,
- TextUtils.join(",", iconBlacklist), mCurrentUser);
- }
- }
- setValue(TUNER_VERSION, newVersion);
- }
-
- public String getValue(String setting) {
- return Settings.Secure.getStringForUser(mContentResolver, setting, mCurrentUser);
- }
-
- public void setValue(String setting, String value) {
- Settings.Secure.putStringForUser(mContentResolver, setting, value, mCurrentUser);
- }
-
- public int getValue(String setting, int def) {
- return Settings.Secure.getIntForUser(mContentResolver, setting, def, mCurrentUser);
- }
-
- public String getValue(String setting, String def) {
- String ret = Secure.getStringForUser(mContentResolver, setting, mCurrentUser);
- if (ret == null) return def;
- return ret;
- }
-
- public void setValue(String setting, int value) {
- Settings.Secure.putIntForUser(mContentResolver, setting, value, mCurrentUser);
- }
-
- public void addTunable(Tunable tunable, String... keys) {
- for (String key : keys) {
- addTunable(tunable, key);
- }
- }
-
- private void addTunable(Tunable tunable, String key) {
- if (!mTunableLookup.containsKey(key)) {
- mTunableLookup.put(key, new ArraySet<Tunable>());
- }
- mTunableLookup.get(key).add(tunable);
- if (LeakDetector.ENABLED) {
- mTunables.add(tunable);
- Dependency.get(LeakDetector.class).trackCollection(mTunables, "TunerService.mTunables");
- }
- Uri uri = Settings.Secure.getUriFor(key);
- if (!mListeningUris.containsKey(uri)) {
- mListeningUris.put(uri, key);
- mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
- }
- // Send the first state.
- String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
- tunable.onTuningChanged(key, value);
- }
-
- public void removeTunable(Tunable tunable) {
- for (Set<Tunable> list : mTunableLookup.values()) {
- list.remove(tunable);
- }
- if (LeakDetector.ENABLED) {
- mTunables.remove(tunable);
- }
- }
-
- protected void reregisterAll() {
- if (mListeningUris.size() == 0) {
- return;
- }
- mContentResolver.unregisterContentObserver(mObserver);
- for (Uri uri : mListeningUris.keySet()) {
- mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
- }
- }
-
- public void reloadSetting(Uri uri) {
- String key = mListeningUris.get(uri);
- Set<Tunable> tunables = mTunableLookup.get(key);
- if (tunables == null) {
- return;
- }
- String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
- for (Tunable tunable : tunables) {
- tunable.onTuningChanged(key, value);
- }
- }
-
- private void reloadAll() {
- for (String key : mTunableLookup.keySet()) {
- String value = Settings.Secure.getStringForUser(mContentResolver, key,
- mCurrentUser);
- for (Tunable tunable : mTunableLookup.get(key)) {
- tunable.onTuningChanged(key, value);
- }
- }
- }
-
- public void clearAll() {
- // A couple special cases.
- Settings.Global.putString(mContentResolver, DemoMode.DEMO_MODE_ALLOWED, null);
- Settings.System.putString(mContentResolver,
- SHOW_BATTERY_PERCENT, null);
- Intent intent = new Intent(DemoMode.ACTION_DEMO);
- intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT);
- mContext.sendBroadcast(intent);
-
- for (String key : mTunableLookup.keySet()) {
- Settings.Secure.putString(mContentResolver, key, null);
- }
}
public static final void showResetRequest(final Context context, final Runnable onDisabled) {
@@ -247,59 +115,4 @@
});
dialog.show();
}
-
- public static final void setTunerEnabled(Context context, boolean enabled) {
- userContext(context).getPackageManager().setComponentEnabledSetting(
- new ComponentName(context, TunerActivity.class),
- enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
- : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
- PackageManager.DONT_KILL_APP);
-
- userContext(context).getPackageManager().setComponentEnabledSetting(
- new ComponentName(context, TunerActivity.ACTIVITY_ALIAS_NAME),
- enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
- : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
- PackageManager.DONT_KILL_APP);
- }
-
- public static final boolean isTunerEnabled(Context context) {
- return userContext(context).getPackageManager().getComponentEnabledSetting(
- new ComponentName(context, TunerActivity.class))
- == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
- }
-
- private static Context userContext(Context context) {
- try {
- return context.createPackageContextAsUser(context.getPackageName(), 0,
- new UserHandle(ActivityManager.getCurrentUser()));
- } catch (NameNotFoundException e) {
- return context;
- }
- }
-
- private class Observer extends ContentObserver {
- public Observer() {
- super(new Handler(Looper.getMainLooper()));
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
- if (userId == ActivityManager.getCurrentUser()) {
- reloadSetting(uri);
- }
- }
- }
-
- public interface Tunable {
- void onTuningChanged(String key, String newValue);
- }
-
- public static class ClearReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (ACTION_CLEAR.equals(intent.getAction())) {
- Dependency.get(TunerService.class).clearAll();
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
new file mode 100644
index 0000000..8e584bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2015 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.tuner;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.systemui.DemoMode;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.SystemUI;
+import com.android.systemui.SystemUIApplication;
+import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.util.leak.LeakDetector;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+
+public class TunerServiceImpl extends TunerService {
+
+ private static final String TUNER_VERSION = "sysui_tuner_version";
+
+ private static final int CURRENT_TUNER_VERSION = 1;
+
+ private final Observer mObserver = new Observer();
+ // Map of Uris we listen on to their settings keys.
+ private final ArrayMap<Uri, String> mListeningUris = new ArrayMap<>();
+ // Map of settings keys to the listener.
+ private final HashMap<String, Set<Tunable>> mTunableLookup = new HashMap<>();
+ // Set of all tunables, used for leak detection.
+ private final HashSet<Tunable> mTunables = LeakDetector.ENABLED ? new HashSet<>() : null;
+ private final Context mContext;
+
+ private ContentResolver mContentResolver;
+ private int mCurrentUser;
+ private CurrentUserTracker mUserTracker;
+
+ public TunerServiceImpl(Context context) {
+ mContext = context;
+ mContentResolver = mContext.getContentResolver();
+
+ for (UserInfo user : UserManager.get(mContext).getUsers()) {
+ mCurrentUser = user.getUserHandle().getIdentifier();
+ if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) {
+ upgradeTuner(getValue(TUNER_VERSION, 0), CURRENT_TUNER_VERSION);
+ }
+ }
+
+ mCurrentUser = ActivityManager.getCurrentUser();
+ mUserTracker = new CurrentUserTracker(mContext) {
+ @Override
+ public void onUserSwitched(int newUserId) {
+ mCurrentUser = newUserId;
+ reloadAll();
+ reregisterAll();
+ }
+ };
+ mUserTracker.startTracking();
+ }
+
+ @Override
+ public void destroy() {
+ mUserTracker.stopTracking();
+ }
+
+ private void upgradeTuner(int oldVersion, int newVersion) {
+ if (oldVersion < 1) {
+ String blacklistStr = getValue(StatusBarIconController.ICON_BLACKLIST);
+ if (blacklistStr != null) {
+ ArraySet<String> iconBlacklist =
+ StatusBarIconController.getIconBlacklist(blacklistStr);
+
+ iconBlacklist.add("rotate");
+ iconBlacklist.add("headset");
+
+ Settings.Secure.putStringForUser(mContentResolver,
+ StatusBarIconController.ICON_BLACKLIST,
+ TextUtils.join(",", iconBlacklist), mCurrentUser);
+ }
+ }
+ setValue(TUNER_VERSION, newVersion);
+ }
+
+ @Override
+ public String getValue(String setting) {
+ return Settings.Secure.getStringForUser(mContentResolver, setting, mCurrentUser);
+ }
+
+ @Override
+ public void setValue(String setting, String value) {
+ Settings.Secure.putStringForUser(mContentResolver, setting, value, mCurrentUser);
+ }
+
+ @Override
+ public int getValue(String setting, int def) {
+ return Settings.Secure.getIntForUser(mContentResolver, setting, def, mCurrentUser);
+ }
+
+ @Override
+ public String getValue(String setting, String def) {
+ String ret = Secure.getStringForUser(mContentResolver, setting, mCurrentUser);
+ if (ret == null) return def;
+ return ret;
+ }
+
+ @Override
+ public void setValue(String setting, int value) {
+ Settings.Secure.putIntForUser(mContentResolver, setting, value, mCurrentUser);
+ }
+
+ @Override
+ public void addTunable(Tunable tunable, String... keys) {
+ for (String key : keys) {
+ addTunable(tunable, key);
+ }
+ }
+
+ private void addTunable(Tunable tunable, String key) {
+ if (!mTunableLookup.containsKey(key)) {
+ mTunableLookup.put(key, new ArraySet<Tunable>());
+ }
+ mTunableLookup.get(key).add(tunable);
+ if (LeakDetector.ENABLED) {
+ mTunables.add(tunable);
+ Dependency.get(LeakDetector.class).trackCollection(mTunables, "TunerService.mTunables");
+ }
+ Uri uri = Settings.Secure.getUriFor(key);
+ if (!mListeningUris.containsKey(uri)) {
+ mListeningUris.put(uri, key);
+ mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
+ }
+ // Send the first state.
+ String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
+ tunable.onTuningChanged(key, value);
+ }
+
+ @Override
+ public void removeTunable(Tunable tunable) {
+ for (Set<Tunable> list : mTunableLookup.values()) {
+ list.remove(tunable);
+ }
+ if (LeakDetector.ENABLED) {
+ mTunables.remove(tunable);
+ }
+ }
+
+ protected void reregisterAll() {
+ if (mListeningUris.size() == 0) {
+ return;
+ }
+ mContentResolver.unregisterContentObserver(mObserver);
+ for (Uri uri : mListeningUris.keySet()) {
+ mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
+ }
+ }
+
+ private void reloadSetting(Uri uri) {
+ String key = mListeningUris.get(uri);
+ Set<Tunable> tunables = mTunableLookup.get(key);
+ if (tunables == null) {
+ return;
+ }
+ String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
+ for (Tunable tunable : tunables) {
+ tunable.onTuningChanged(key, value);
+ }
+ }
+
+ private void reloadAll() {
+ for (String key : mTunableLookup.keySet()) {
+ String value = Settings.Secure.getStringForUser(mContentResolver, key,
+ mCurrentUser);
+ for (Tunable tunable : mTunableLookup.get(key)) {
+ tunable.onTuningChanged(key, value);
+ }
+ }
+ }
+
+ @Override
+ public void clearAll() {
+ // A couple special cases.
+ Settings.Global.putString(mContentResolver, DemoMode.DEMO_MODE_ALLOWED, null);
+ Intent intent = new Intent(DemoMode.ACTION_DEMO);
+ intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT);
+ mContext.sendBroadcast(intent);
+
+ for (String key : mTunableLookup.keySet()) {
+ Settings.Secure.putString(mContentResolver, key, null);
+ }
+ }
+
+ private class Observer extends ContentObserver {
+ public Observer() {
+ super(new Handler(Looper.getMainLooper()));
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ if (userId == ActivityManager.getCurrentUser()) {
+ reloadSetting(uri);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java b/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java
deleted file mode 100644
index 5cfe677..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java
+++ /dev/null
@@ -1,174 +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.util;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Builder class to create a {@link LayoutInflater} with various properties.
- *
- * Call any desired configuration methods on the Builder and then use
- * {@link Builder#build} to create the LayoutInflater. This is an alternative to directly using
- * {@link LayoutInflater#setFilter} and {@link LayoutInflater#setFactory}.
- * @hide for use by framework
- */
-public class LayoutInflaterBuilder {
- private static final String TAG = "LayoutInflaterBuilder";
-
- private Context mFromContext;
- private Context mTargetContext;
- private Map<String, String> mReplaceMap;
- private Set<Class> mDisallowedClasses;
- private LayoutInflater mBuiltInflater;
-
- /**
- * Creates a new Builder which will construct a LayoutInflater.
- *
- * @param fromContext This context's LayoutInflater will be cloned by the Builder using
- * {@link LayoutInflater#cloneInContext}. By default, the new LayoutInflater will point at
- * this same Context.
- */
- public LayoutInflaterBuilder(@NonNull Context fromContext) {
- mFromContext = fromContext;
- mTargetContext = fromContext;
- mReplaceMap = null;
- mDisallowedClasses = null;
- mBuiltInflater = null;
- }
-
- /**
- * Instructs the Builder to point the LayoutInflater at a different Context.
- *
- * @param targetContext Context to be provided to
- * {@link LayoutInflater#cloneInContext(Context)}.
- * @return Builder object post-modification.
- */
- public LayoutInflaterBuilder target(@NonNull Context targetContext) {
- assertIfAlreadyBuilt();
- mTargetContext = targetContext;
- return this;
- }
-
- /**
- * Instructs the Builder to configure the LayoutInflater such that all instances
- * of one {@link View} will be replaced with instances of another during inflation.
- *
- * @param from Instances of this class will be replaced during inflation.
- * @param to Instances of this class will be inflated as replacements.
- * @return Builder object post-modification.
- */
- public LayoutInflaterBuilder replace(@NonNull Class from, @NonNull Class to) {
- return replace(from.getName(), to);
- }
-
- /**
- * Instructs the Builder to configure the LayoutInflater such that all instances
- * of one {@link View} will be replaced with instances of another during inflation.
- *
- * @param tag Instances of this tag will be replaced during inflation.
- * @param to Instances of this class will be inflated as replacements.
- * @return Builder object post-modification.
- */
- public LayoutInflaterBuilder replace(@NonNull String tag, @NonNull Class to) {
- assertIfAlreadyBuilt();
- if (mReplaceMap == null) {
- mReplaceMap = new ArrayMap<String, String>();
- }
- mReplaceMap.put(tag, to.getName());
- return this;
- }
-
- /**
- * Instructs the Builder to configure the LayoutInflater such that any attempt to inflate
- * a {@link View} of a given type will throw a {@link InflateException}.
- *
- * @param disallowedClass The Class type that will be disallowed.
- * @return Builder object post-modification.
- */
- public LayoutInflaterBuilder disallow(@NonNull Class disallowedClass) {
- assertIfAlreadyBuilt();
- if (mDisallowedClasses == null) {
- mDisallowedClasses = new ArraySet<Class>();
- }
- mDisallowedClasses.add(disallowedClass);
- return this;
- }
-
- /**
- * Builds and returns the LayoutInflater. Afterwards, this Builder can no longer can be
- * used, all future calls on the Builder will throw {@link AssertionError}.
- */
- public LayoutInflater build() {
- assertIfAlreadyBuilt();
- mBuiltInflater =
- LayoutInflater.from(mFromContext).cloneInContext(mTargetContext);
- setFactoryIfNeeded(mBuiltInflater);
- setFilterIfNeeded(mBuiltInflater);
- return mBuiltInflater;
- }
-
- private void assertIfAlreadyBuilt() {
- if (mBuiltInflater != null) {
- throw new AssertionError("Cannot use this Builder after build() has been called.");
- }
- }
-
- private void setFactoryIfNeeded(LayoutInflater inflater) {
- if (mReplaceMap == null) {
- return;
- }
- inflater.setFactory(
- new LayoutInflater.Factory() {
- @Override
- public View onCreateView(String name, Context context, AttributeSet attrs) {
- String replacingClassName = mReplaceMap.get(name);
- if (replacingClassName != null) {
- try {
- return inflater.createView(replacingClassName, null, attrs);
- } catch (ClassNotFoundException e) {
- Log.e(TAG, "Could not replace " + name
- + " with " + replacingClassName
- + ", Exception: ", e);
- }
- }
- return null;
- }
- });
- }
-
- private void setFilterIfNeeded(LayoutInflater inflater) {
- if (mDisallowedClasses == null) {
- return;
- }
- inflater.setFilter(
- new LayoutInflater.Filter() {
- @Override
- public boolean onLoadClass(Class clazz) {
- return !mDisallowedClasses.contains(clazz);
- }
- });
- }
-}