From a82b62678a0e1eaba50ec5adce93862683dac065 Mon Sep 17 00:00:00 2001 From: Adam Lesinski Date: Tue, 21 Mar 2017 16:56:17 -0700 Subject: Themes: Apply themes to system_server safely Creates a new UI Context for UI based operations. This UI Context is wired up the same way a normal app Context would be, and is subject to change when overlays are enabled/disabled. For this reason, only UI should be using this new Context. All other operations should be using the original system Context so that changing themes don't impact the regular operations of system_server. Also added some sanity checks at key places where we show UI (ShutdownThread, BaseErrorDialog). Bug: 36059431 Test: $ adb shell am crash com.android.settings Test: Observe crash and power off dialogs are blue with PixelTheme Change-Id: I87227ee2e0be1e72dcde8f482b37725cb687260b --- core/java/android/app/ActivityThread.java | 21 ++++++++++-- .../android/app/ApplicationPackageManager.java | 4 +-- core/java/android/app/ContextImpl.java | 12 +++++++ core/java/android/app/ResourcesManager.java | 2 +- core/java/android/content/Context.java | 14 ++++++++ .../java/com/android/server/SystemService.java | 11 ++++++ .../android/server/am/ActivityManagerService.java | 39 +++++++++++++--------- .../core/java/com/android/server/am/AppErrors.java | 1 + .../com/android/server/am/BaseErrorDialog.java | 1 + .../android/server/power/PowerManagerService.java | 6 ++-- .../com/android/server/power/ShutdownThread.java | 17 +++++++--- .../server/statusbar/StatusBarManagerService.java | 8 +++-- .../android/server/wm/WindowManagerService.java | 13 ++++++-- services/java/com/android/server/SystemServer.java | 3 ++ 14 files changed, 119 insertions(+), 33 deletions(-) diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e89dc0bd1956..6351d53383f8 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -221,6 +221,7 @@ public final class ActivityThread { private long mNetworkBlockSeq = INVALID_PROC_STATE_SEQ; private ContextImpl mSystemContext; + private ContextImpl mSystemUiContext; static volatile IPackageManager sPackageManager; @@ -2178,9 +2179,19 @@ public final class ActivityThread { } } + public ContextImpl getSystemUiContext() { + synchronized (this) { + if (mSystemUiContext == null) { + mSystemUiContext = ContextImpl.createSystemUiContext(this); + } + return mSystemUiContext; + } + } + public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { synchronized (this) { getSystemContext().installSystemApplicationInfo(info, classLoader); + getSystemUiContext().installSystemApplicationInfo(info, classLoader); // give ourselves a default profiler mProfiler = new Profiler(); @@ -5003,6 +5014,11 @@ public final class ActivityThread { if ((systemTheme.getChangingConfigurations() & configDiff) != 0) { systemTheme.rebase(); } + + final Theme systemUiTheme = getSystemUiContext().getTheme(); + if ((systemUiTheme.getChangingConfigurations() & configDiff) != 0) { + systemUiTheme.rebase(); + } } ArrayList callbacks = collectComponentCallbacks(false, config); @@ -5058,9 +5074,10 @@ public final class ActivityThread { // Trigger a regular Configuration change event, only with a different assetsSeq number // so that we actually call through to all components. + // TODO(adamlesinski): Change this to make use of ActivityManager's upcoming ability to + // store configurations per-process. Configuration newConfig = new Configuration(); - newConfig.unset(); - newConfig.assetsSeq = mConfiguration.assetsSeq + 1; + newConfig.assetsSeq = (mConfiguration != null ? mConfiguration.assetsSeq : 0) + 1; handleConfigurationChanged(newConfig, null); // Schedule all activities to reload diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 461f9cc35125..a6838f8bbf0a 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1352,7 +1352,7 @@ public class ApplicationPackageManager extends PackageManager { public Resources getResourcesForApplication(@NonNull ApplicationInfo app) throws NameNotFoundException { if (app.packageName.equals("system")) { - return mContext.mMainThread.getSystemContext().getResources(); + return mContext.mMainThread.getSystemUiContext().getResources(); } final boolean sameUid = (app.uid == Process.myUid()); final Resources r = mContext.mMainThread.getTopLevelResources( @@ -1383,7 +1383,7 @@ public class ApplicationPackageManager extends PackageManager { "Call does not support special user #" + userId); } if ("system".equals(appPackageName)) { - return mContext.mMainThread.getSystemContext().getResources(); + return mContext.mMainThread.getSystemUiContext().getResources(); } try { ApplicationInfo ai = mPM.getApplicationInfo(appPackageName, sDefaultFlags, userId); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 4c080c9e6a95..d89142ab1fe4 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2194,6 +2194,18 @@ class ContextImpl extends Context { return context; } + /** + * System Context to be used for UI. This Context has resources that can be themed. + */ + static ContextImpl createSystemUiContext(ActivityThread mainThread) { + LoadedApk packageInfo = new LoadedApk(mainThread); + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, + null); + context.setResources(createResources(null, packageInfo, null, Display.DEFAULT_DISPLAY, null, + packageInfo.getCompatibilityInfo())); + return context; + } + static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) { if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index b42df5e2e0fb..489a0f0975ae 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -984,7 +984,7 @@ public class ResourcesManager { final ResourcesKey key = mResourceImpls.keyAt(i); final WeakReference weakImplRef = mResourceImpls.valueAt(i); final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; - if (impl != null && key.mResDir != null && key.mResDir.equals(baseCodePath)) { + if (impl != null && (key.mResDir == null || key.mResDir.equals(baseCodePath))) { updatedResourceKeys.put(impl, new ResourcesKey( key.mResDir, key.mSplitResDirs, diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 3a8a4206e105..5e8df42fc12e 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4559,4 +4559,18 @@ public abstract class Context { public Handler getMainThreadHandler() { throw new RuntimeException("Not implemented. Must override in a subclass."); } + + /** + * Throws an exception if the Context is using system resources, + * which are non-runtime-overlay-themable and may show inconsistent UI. + * @hide + */ + public void assertRuntimeOverlayThemable() { + // Resources.getSystem() is a singleton and the only Resources not managed by + // ResourcesManager; therefore Resources.getSystem() is not themable. + if (getResources() == Resources.getSystem()) { + throw new IllegalArgumentException("Non-UI context used to display UI; " + + "get a UI context from ActivityThread#getSystemUiContext()"); + } + } } diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java index 421d5a6ab964..c105b1244e74 100644 --- a/services/core/java/com/android/server/SystemService.java +++ b/services/core/java/com/android/server/SystemService.java @@ -16,6 +16,7 @@ package com.android.server; +import android.app.ActivityThread; import android.content.Context; import android.os.IBinder; import android.os.ServiceManager; @@ -103,6 +104,16 @@ public abstract class SystemService { return mContext; } + /** + * Get the system UI context. This context is to be used for displaying UI. It is themable, + * which means resources can be overridden at runtime. Do not use to retrieve properties that + * configure the behavior of the device that is not UX related. + */ + public final Context getUiContext() { + // This has already been set up by the time any SystemServices are created. + return ActivityThread.currentActivityThread().getSystemUiContext(); + } + /** * Returns true if the system is running in safe mode. * TODO: we should define in which phase this becomes valid diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ce9a904fd6af..f2074620cab5 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1343,7 +1343,13 @@ public class ActivityManagerService extends IActivityManager.Stub @GuardedBy("this") boolean mLaunchWarningShown = false; @GuardedBy("this") boolean mCheckedForSetup = false; - Context mContext; + final Context mContext; + + /** + * This Context is themable and meant for UI display (AlertDialogs, etc.). The theme can + * change at runtime. Use mContext for non-UI purposes. + */ + final Context mUiContext; /** * The time at which we will allow normal application switches again, @@ -1844,7 +1850,7 @@ public class ActivityManagerService extends IActivityManager.Stub } break; case SHOW_FACTORY_ERROR_UI_MSG: { Dialog d = new FactoryErrorDialog( - mContext, msg.getData().getCharSequence("msg")); + mUiContext, msg.getData().getCharSequence("msg")); d.show(); ensureBootCompleted(); } break; @@ -1855,7 +1861,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (!app.waitedForDebugger) { Dialog d = new AppWaitingForDebuggerDialog( ActivityManagerService.this, - mContext, app); + mUiContext, app); app.waitDialog = d; app.waitedForDebugger = true; d.show(); @@ -1870,24 +1876,24 @@ public class ActivityManagerService extends IActivityManager.Stub } break; case SHOW_UID_ERROR_UI_MSG: { if (mShowDialogs) { - AlertDialog d = new BaseErrorDialog(mContext); + AlertDialog d = new BaseErrorDialog(mUiContext); d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); d.setCancelable(false); - d.setTitle(mContext.getText(R.string.android_system_label)); - d.setMessage(mContext.getText(R.string.system_error_wipe_data)); - d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok), + d.setTitle(mUiContext.getText(R.string.android_system_label)); + d.setMessage(mUiContext.getText(R.string.system_error_wipe_data)); + d.setButton(DialogInterface.BUTTON_POSITIVE, mUiContext.getText(R.string.ok), obtainMessage(DISMISS_DIALOG_UI_MSG, d)); d.show(); } } break; case SHOW_FINGERPRINT_ERROR_UI_MSG: { if (mShowDialogs) { - AlertDialog d = new BaseErrorDialog(mContext); + AlertDialog d = new BaseErrorDialog(mUiContext); d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); d.setCancelable(false); - d.setTitle(mContext.getText(R.string.android_system_label)); - d.setMessage(mContext.getText(R.string.system_error_manufacturer)); - d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok), + d.setTitle(mUiContext.getText(R.string.android_system_label)); + d.setMessage(mUiContext.getText(R.string.system_error_manufacturer)); + d.setButton(DialogInterface.BUTTON_POSITIVE, mUiContext.getText(R.string.ok), obtainMessage(DISMISS_DIALOG_UI_MSG, d)); d.show(); } @@ -1911,7 +1917,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (mode == ActivityManager.COMPAT_MODE_DISABLED || mode == ActivityManager.COMPAT_MODE_ENABLED) { mCompatModeDialog = new CompatModeDialog( - ActivityManagerService.this, mContext, + ActivityManagerService.this, mUiContext, ar.info.applicationInfo); mCompatModeDialog.show(); } @@ -1931,7 +1937,7 @@ public class ActivityManagerService extends IActivityManager.Stub ar.packageName)) { // TODO(multi-display): Show dialog on appropriate display. mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog( - ActivityManagerService.this, mContext, ar.info.applicationInfo); + ActivityManagerService.this, mUiContext, ar.info.applicationInfo); mUnsupportedDisplaySizeDialog.show(); } } @@ -2744,6 +2750,7 @@ public class ActivityManagerService extends IActivityManager.Stub public ActivityManagerService(Injector injector) { mInjector = injector; mContext = mInjector.getContext(); + mUiContext = null; GL_ES_VERSION = 0; mActivityStarter = null; mAppErrors = null; @@ -2775,8 +2782,10 @@ public class ActivityManagerService extends IActivityManager.Stub LockGuard.installLock(this, LockGuard.INDEX_ACTIVITY); mInjector = new Injector(); mContext = systemContext; + mFactoryTest = FactoryTest.getMode(); mSystemThread = ActivityThread.currentActivityThread(); + mUiContext = mSystemThread.getSystemUiContext(); Slog.i(TAG, "Memory class: " + ActivityManager.staticGetMemoryClass()); @@ -2819,7 +2828,7 @@ public class ActivityManagerService extends IActivityManager.Stub mServices = new ActiveServices(this); mProviderMap = new ProviderMap(this); - mAppErrors = new AppErrors(mContext, this); + mAppErrors = new AppErrors(mUiContext, this); // TODO: Move creation of battery stats service outside of activity manager service. File dataDir = Environment.getDataDirectory(); @@ -23824,7 +23833,7 @@ public class ActivityManagerService extends IActivityManager.Stub final boolean updateFrameworkRes = packagesToUpdate.contains("android"); for (int i = mLruProcesses.size() - 1; i >= 0; i--) { final ProcessRecord app = mLruProcesses.get(i); - if (app.thread == null || app.pid == Process.myPid()) { + if (app.thread == null) { continue; } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index c10f77cefbe4..1c14597e539b 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -98,6 +98,7 @@ class AppErrors { AppErrors(Context context, ActivityManagerService service) { + context.assertRuntimeOverlayThemable(); mService = service; mContext = context; } diff --git a/services/core/java/com/android/server/am/BaseErrorDialog.java b/services/core/java/com/android/server/am/BaseErrorDialog.java index 5f7f67a8a5e8..347a357aae04 100644 --- a/services/core/java/com/android/server/am/BaseErrorDialog.java +++ b/services/core/java/com/android/server/am/BaseErrorDialog.java @@ -34,6 +34,7 @@ class BaseErrorDialog extends AlertDialog { public BaseErrorDialog(Context context) { super(context, com.android.internal.R.style.Theme_Dialog_AppError); + context.assertRuntimeOverlayThemable(); getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 4f67e8ce2d15..26151e32665e 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -2670,11 +2670,11 @@ public final class PowerManagerService extends SystemService public void run() { synchronized (this) { if (haltMode == HALT_MODE_REBOOT_SAFE_MODE) { - ShutdownThread.rebootSafeMode(mContext, confirm); + ShutdownThread.rebootSafeMode(getUiContext(), confirm); } else if (haltMode == HALT_MODE_REBOOT) { - ShutdownThread.reboot(mContext, reason, confirm); + ShutdownThread.reboot(getUiContext(), reason, confirm); } else { - ShutdownThread.shutdown(mContext, reason, confirm); + ShutdownThread.shutdown(getUiContext(), reason, confirm); } } } diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index 841e2a158c4f..864e83ef1f86 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -79,7 +79,7 @@ public final class ShutdownThread extends Thread { private static final int SHUTDOWN_VIBRATE_MS = 500; // state tracking - private static Object sIsStartedGuard = new Object(); + private static final Object sIsStartedGuard = new Object(); private static boolean sIsStarted = false; private static boolean mReboot; @@ -121,7 +121,8 @@ public final class ShutdownThread extends Thread { * state etc. Must be called from a Looper thread in which its UI * is shown. * - * @param context Context used to display the shutdown progress dialog. + * @param context Context used to display the shutdown progress dialog. This must be a context + * suitable for displaying UI (aka Themable). * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null. * @param confirm true if user confirmation is needed before shutting down. */ @@ -132,7 +133,11 @@ public final class ShutdownThread extends Thread { shutdownInner(context, confirm); } - static void shutdownInner(final Context context, boolean confirm) { + private static void shutdownInner(final Context context, boolean confirm) { + // ShutdownThread is called from many places, so best to verify here that the context passed + // in is themed. + context.assertRuntimeOverlayThemable(); + // ensure that only one thread is trying to power down. // any additional calls are just returned synchronized (sIsStartedGuard) { @@ -204,7 +209,8 @@ public final class ShutdownThread extends Thread { * state etc. Must be called from a Looper thread in which its UI * is shown. * - * @param context Context used to display the shutdown progress dialog. + * @param context Context used to display the shutdown progress dialog. This must be a context + * suitable for displaying UI (aka Themable). * @param reason code to pass to the kernel (e.g. "recovery"), or null. * @param confirm true if user confirmation is needed before shutting down. */ @@ -220,7 +226,8 @@ public final class ShutdownThread extends Thread { * Request a reboot into safe mode. Must be called from a Looper thread in which its UI * is shown. * - * @param context Context used to display the shutdown progress dialog. + * @param context Context used to display the shutdown progress dialog. This must be a context + * suitable for displaying UI (aka Themable). * @param confirm true if user confirmation is needed before shutting down. */ public static void rebootSafeMode(final Context context, boolean confirm) { diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index aaaa0805b3c4..66502415e29e 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -16,6 +16,7 @@ package com.android.server.statusbar; +import android.app.ActivityThread; import android.app.StatusBarManager; import android.content.ComponentName; import android.content.Context; @@ -60,6 +61,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub { private static final boolean SPEW = false; private final Context mContext; + private final WindowManagerService mWindowManager; private Handler mHandler = new Handler(); private NotificationDelegate mNotificationDelegate; @@ -776,10 +778,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub { long identity = Binder.clearCallingIdentity(); try { mHandler.post(() -> { + // ShutdownThread displays UI, so give it a UI context. + Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext(); if (safeMode) { - ShutdownThread.rebootSafeMode(mContext, false); + ShutdownThread.rebootSafeMode(uiContext, false); } else { - ShutdownThread.reboot(mContext, PowerManager.SHUTDOWN_USER_REQUESTED, false); + ShutdownThread.reboot(uiContext, PowerManager.SHUTDOWN_USER_REQUESTED, false); } }); } finally { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0dc74d72635b..f4e3fde8ac3d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -102,6 +102,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityManagerInternal; +import android.app.ActivityThread; import android.app.AppOpsManager; import android.app.IActivityManager; import android.content.BroadcastReceiver; @@ -3195,19 +3196,25 @@ public class WindowManagerService extends IWindowManager.Stub // Called by window manager policy. Not exposed externally. @Override public void shutdown(boolean confirm) { - ShutdownThread.shutdown(mContext, PowerManager.SHUTDOWN_USER_REQUESTED, confirm); + // Pass in the UI context, since ShutdownThread requires it (to show UI). + ShutdownThread.shutdown(ActivityThread.currentActivityThread().getSystemUiContext(), + PowerManager.SHUTDOWN_USER_REQUESTED, confirm); } // Called by window manager policy. Not exposed externally. @Override public void reboot(boolean confirm) { - ShutdownThread.reboot(mContext, PowerManager.SHUTDOWN_USER_REQUESTED, confirm); + // Pass in the UI context, since ShutdownThread requires it (to show UI). + ShutdownThread.reboot(ActivityThread.currentActivityThread().getSystemUiContext(), + PowerManager.SHUTDOWN_USER_REQUESTED, confirm); } // Called by window manager policy. Not exposed externally. @Override public void rebootSafeMode(boolean confirm) { - ShutdownThread.rebootSafeMode(mContext, confirm); + // Pass in the UI context, since ShutdownThread requires it (to show UI). + ShutdownThread.rebootSafeMode(ActivityThread.currentActivityThread().getSystemUiContext(), + confirm); } public void setCurrentProfileIds(final int[] currentProfileIds) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index da49eb37ebec..412cf81eb5a5 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -475,6 +475,9 @@ public final class SystemServer { ActivityThread activityThread = ActivityThread.systemMain(); mSystemContext = activityThread.getSystemContext(); mSystemContext.setTheme(DEFAULT_SYSTEM_THEME); + + final Context systemUiContext = activityThread.getSystemUiContext(); + systemUiContext.setTheme(DEFAULT_SYSTEM_THEME); } /** -- cgit v1.2.3-59-g8ed1b