diff options
| -rw-r--r-- | core/java/android/app/ActivityThread.java | 38 | ||||
| -rw-r--r-- | core/java/android/content/res/AssetManager.java | 11 | ||||
| -rw-r--r-- | core/java/android/content/res/Resources.java | 5 | ||||
| -rw-r--r-- | core/java/android/content/res/ResourcesImpl.java | 4 | ||||
| -rw-r--r-- | core/jni/android_util_AssetManager.cpp | 15 | ||||
| -rw-r--r-- | core/res/res/values/config.xml | 6 | ||||
| -rw-r--r-- | core/res/res/values/symbols.xml | 1 | ||||
| -rw-r--r-- | services/core/java/com/android/server/wm/ActivityRecord.java | 65 | ||||
| -rw-r--r-- | services/core/java/com/android/server/wm/WindowManagerService.java | 12 | ||||
| -rw-r--r-- | services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java | 57 |
10 files changed, 210 insertions, 4 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b26504dd1c49..5985d6e93107 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -29,6 +29,8 @@ import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE; import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS; import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX; +import static android.content.res.Configuration.UI_MODE_TYPE_DESK; +import static android.content.res.Configuration.UI_MODE_TYPE_MASK; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded; @@ -194,6 +196,7 @@ import android.window.SplashScreen; import android.window.SplashScreenView; import android.window.WindowProviderService; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; @@ -5890,9 +5893,21 @@ public final class ActivityThread extends ClientTransactionHandler final boolean shouldUpdateResources = hasPublicResConfigChange || shouldUpdateResources(activityToken, currentResConfig, newConfig, amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange); + + // TODO(b/266924897): temporary workaround, remove for U. + boolean skipActivityRelaunchWhenDocking = activity.getResources().getBoolean( + R.bool.config_skipActivityRelaunchWhenDocking); + int handledConfigChanges = activity.mActivityInfo.getRealConfigChanged(); + if (skipActivityRelaunchWhenDocking && onlyDeskInUiModeChanged(activity.mCurrentConfig, + newConfig)) { + // If we're not relaunching this activity when docking, we should send the configuration + // changed event. Pretend as if the activity is handling uiMode config changes in its + // manifest so that we'll report any dock changes. + handledConfigChanges |= ActivityInfo.CONFIG_UI_MODE; + } + final boolean shouldReportChange = shouldReportChange(activity.mCurrentConfig, newConfig, - r != null ? r.mSizeConfigurations : null, - activity.mActivityInfo.getRealConfigChanged()); + r != null ? r.mSizeConfigurations : null, handledConfigChanges); // Nothing significant, don't proceed with updating and reporting. if (!shouldUpdateResources && !shouldReportChange) { return null; @@ -5939,6 +5954,25 @@ public final class ActivityThread extends ClientTransactionHandler } /** + * Returns true if the uiMode configuration changed, and desk mode + * ({@link android.content.res.Configuration#UI_MODE_TYPE_DESK}) was the only change to uiMode. + */ + private boolean onlyDeskInUiModeChanged(Configuration oldConfig, Configuration newConfig) { + boolean deskModeChanged = isInDeskUiMode(oldConfig) != isInDeskUiMode(newConfig); + + // UI mode contains fields other than the UI mode type, so determine if any other fields + // changed. + boolean uiModeOtherFieldsChanged = + (oldConfig.uiMode & ~UI_MODE_TYPE_MASK) != (newConfig.uiMode & ~UI_MODE_TYPE_MASK); + + return deskModeChanged && !uiModeOtherFieldsChanged; + } + + private static boolean isInDeskUiMode(Configuration config) { + return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_DESK; + } + + /** * Returns {@code true} if {@link Activity#onConfigurationChanged(Configuration)} should be * dispatched. * diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index c8bbb0c1994d..6f09e79bdba9 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -1453,6 +1453,16 @@ public final class AssetManager implements AutoCloseable { } /** + * @hide + */ + Configuration[] getSizeAndUiModeConfigurations() { + synchronized (this) { + ensureValidLocked(); + return nativeGetSizeAndUiModeConfigurations(mObject); + } + } + + /** * Change the configuration used when retrieving resources. Not for use by * applications. * @hide @@ -1603,6 +1613,7 @@ public final class AssetManager implements AutoCloseable { private static native @Nullable String nativeGetResourceEntryName(long ptr, @AnyRes int resid); private static native @Nullable String[] nativeGetLocales(long ptr, boolean excludeSystem); private static native @Nullable Configuration[] nativeGetSizeConfigurations(long ptr); + private static native @Nullable Configuration[] nativeGetSizeAndUiModeConfigurations(long ptr); private static native void nativeSetResourceResolutionLoggingEnabled(long ptr, boolean enabled); private static native @Nullable String nativeGetLastResourceResolution(long ptr); diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index a03286d3ec6f..9b169499b41f 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -2207,6 +2207,11 @@ public class Resources { return mResourcesImpl.getSizeConfigurations(); } + /** @hide */ + public Configuration[] getSizeAndUiModeConfigurations() { + return mResourcesImpl.getSizeAndUiModeConfigurations(); + } + /** * Return the compatibility mode information for the application. * The returned object should be treated as read-only. diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index ff072916292b..3bb237ac1228 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -203,6 +203,10 @@ public class ResourcesImpl { return mAssets.getSizeConfigurations(); } + Configuration[] getSizeAndUiModeConfigurations() { + return mAssets.getSizeAndUiModeConfigurations(); + } + CompatibilityInfo getCompatibilityInfo() { return mDisplayAdjustments.getCompatibilityInfo(); } diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 8c23b214b8fe..206ad17e3c4b 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -93,6 +93,7 @@ static struct configuration_offsets_t { jfieldID mScreenWidthDpOffset; jfieldID mScreenHeightDpOffset; jfieldID mScreenLayoutOffset; + jfieldID mUiMode; } gConfigurationOffsets; static struct arraymap_offsets_t { @@ -1027,10 +1028,11 @@ static jobject ConstructConfigurationObject(JNIEnv* env, const ResTable_config& env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp); env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp); env->SetIntField(result, gConfigurationOffsets.mScreenLayoutOffset, config.screenLayout); + env->SetIntField(result, gConfigurationOffsets.mUiMode, config.uiMode); return result; } -static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) { +static jobjectArray GetSizeAndUiModeConfigurations(JNIEnv* env, jlong ptr) { ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); auto configurations = assetmanager->GetResourceConfigurations(true /*exclude_system*/, false /*exclude_mipmap*/); @@ -1057,6 +1059,14 @@ static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, j return array; } +static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) { + return GetSizeAndUiModeConfigurations(env, ptr); +} + +static jobjectArray NativeGetSizeAndUiModeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) { + return GetSizeAndUiModeConfigurations(env, ptr); +} + static jintArray NativeAttributeResolutionStack( JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr, jint xml_style_res, @@ -1487,6 +1497,8 @@ static const JNINativeMethod gAssetManagerMethods[] = { {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales}, {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;", (void*)NativeGetSizeConfigurations}, + {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;", + (void*)NativeGetSizeAndUiModeConfigurations}, // Style attribute related methods. {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack}, @@ -1565,6 +1577,7 @@ int register_android_content_AssetManager(JNIEnv* env) { GetFieldIDOrDie(env, configurationClass, "screenHeightDp", "I"); gConfigurationOffsets.mScreenLayoutOffset = GetFieldIDOrDie(env, configurationClass, "screenLayout", "I"); + gConfigurationOffsets.mUiMode = GetFieldIDOrDie(env, configurationClass, "uiMode", "I"); jclass arrayMapClass = FindClassOrDie(env, "android/util/ArrayMap"); gArrayMapOffsets.classObject = MakeGlobalRefOrDie(env, arrayMapClass); diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 562078c0a654..92d23327e6c4 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5430,6 +5430,12 @@ treatment for stretched issues in camera viewfinder. --> <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool> + <!-- Docking is a uiMode configuration change and will cause activities to relaunch if it's not + handled. If true, the configuration change will be sent but activities will not be + relaunched upon docking. Apps with desk resources will behave like normal, since they may + expect the relaunch upon the desk uiMode change. --> + <bool name="config_skipActivityRelaunchWhenDocking">false</bool> + <!-- If true, hide the display cutout with display area --> <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e21b7f6732f3..d8b261c50cb1 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4491,6 +4491,7 @@ <java-symbol type="bool" name="config_isWindowManagerCameraCompatTreatmentEnabled" /> <java-symbol type="bool" name="config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled" /> <java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" /> + <java-symbol type="bool" name="config_skipActivityRelaunchWhenDocking" /> <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" /> diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index a8626dfe043a..acadf4a03db5 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -100,6 +100,7 @@ import static android.content.res.Configuration.EMPTY; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; +import static android.content.res.Configuration.UI_MODE_TYPE_DESK; import static android.content.res.Configuration.UI_MODE_TYPE_MASK; import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET; import static android.os.Build.VERSION_CODES.HONEYCOMB; @@ -827,6 +828,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** Whether the input to this activity will be dropped during the current playing animation. */ private boolean mIsInputDroppedForAnimation; + /** + * Whether the application has desk mode resources. Calculated and cached when + * {@link #hasDeskResources()} is called. + */ + @Nullable + private Boolean mHasDeskResources; + boolean mHandleExitSplashScreen; @TransferSplashScreenState int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE; @@ -9490,7 +9498,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A configChanged |= CONFIG_UI_MODE; } - return (changes&(~configChanged)) != 0; + // TODO(b/274944389): remove workaround after long-term solution is implemented + // Don't restart due to desk mode change if the app does not have desk resources. + if (mWmService.mSkipActivityRelaunchWhenDocking && onlyDeskInUiModeChanged(changesConfig) + && !hasDeskResources()) { + configChanged |= CONFIG_UI_MODE; + } + + return (changes & (~configChanged)) != 0; } /** @@ -9503,6 +9518,50 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A != isInVrUiMode(lastReportedConfig)); } + /** + * Returns true if the uiMode configuration changed, and desk mode + * ({@link android.content.res.Configuration#UI_MODE_TYPE_DESK}) was the only change to uiMode. + */ + private boolean onlyDeskInUiModeChanged(Configuration lastReportedConfig) { + final Configuration currentConfig = getConfiguration(); + + boolean deskModeChanged = isInDeskUiMode(currentConfig) != isInDeskUiMode( + lastReportedConfig); + // UI mode contains fields other than the UI mode type, so determine if any other fields + // changed. + boolean uiModeOtherFieldsChanged = + (currentConfig.uiMode & ~UI_MODE_TYPE_MASK) != (lastReportedConfig.uiMode + & ~UI_MODE_TYPE_MASK); + + return deskModeChanged && !uiModeOtherFieldsChanged; + } + + /** + * Determines whether or not the application has desk mode resources. + */ + boolean hasDeskResources() { + if (mHasDeskResources != null) { + // We already determined this, return the cached value. + return mHasDeskResources; + } + + mHasDeskResources = false; + try { + Resources packageResources = mAtmService.mContext.createPackageContextAsUser( + packageName, 0, UserHandle.of(mUserId)).getResources(); + for (Configuration sizeConfiguration : + packageResources.getSizeAndUiModeConfigurations()) { + if (isInDeskUiMode(sizeConfiguration)) { + mHasDeskResources = true; + break; + } + } + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Exception thrown during checking for desk resources " + this, e); + } + return mHasDeskResources; + } + private int getConfigurationChanges(Configuration lastReportedConfig) { // Determine what has changed. May be nothing, if this is a config that has come back from // the app after going idle. In that case we just want to leave the official config object @@ -9798,6 +9857,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET; } + private static boolean isInDeskUiMode(Configuration config) { + return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_DESK; + } + String getProcessName() { return info.applicationInfo.processName; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index ccce558b73ce..39165dd0bfa9 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -552,6 +552,16 @@ public class WindowManagerService extends IWindowManager.Stub // everything else on screen). Otherwise, it will be put under always-on-top stacks. final boolean mAssistantOnTopOfDream; + /** + * If true, don't relaunch the activity upon receiving a configuration change to transition to + * or from the {@link UI_MODE_TYPE_DESK} uiMode, which is sent when docking. The configuration + * change will still be sent regardless, only the relaunch is skipped. Apps with desk resources + * are exempt from this and will behave like normal, since they may expect the relaunch upon the + * desk uiMode change. + */ + @VisibleForTesting + boolean mSkipActivityRelaunchWhenDocking; + final boolean mLimitedAlphaCompositing; final int mMaxUiWidth; @@ -1226,6 +1236,8 @@ public class WindowManagerService extends IWindowManager.Stub com.android.internal.R.bool.config_perDisplayFocusEnabled); mAssistantOnTopOfDream = context.getResources().getBoolean( com.android.internal.R.bool.config_assistantOnTopOfDream); + mSkipActivityRelaunchWhenDocking = context.getResources() + .getBoolean(R.bool.config_skipActivityRelaunchWhenDocking); mLetterboxConfiguration = new LetterboxConfiguration( // Using SysUI context to have access to Material colors extracted from Wallpaper. diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 0300eb06c220..2cc752cd1b1a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -45,6 +45,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.content.res.Configuration.UI_MODE_TYPE_DESK; import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static android.os.Process.NOBODY_UID; import static android.view.Display.DEFAULT_DISPLAY; @@ -490,6 +491,62 @@ public class ActivityRecordTests extends WindowTestsBase { } @Test + public void testDeskModeChange_doesNotRelaunch() throws RemoteException { + mWm.mSkipActivityRelaunchWhenDocking = true; + + final ActivityRecord activity = createActivityWithTask(); + // The activity will already be relaunching out of the gate, finish the relaunch so we can + // test properly. + activity.finishRelaunching(); + // Clear out any calls to scheduleTransaction from launching the activity. + reset(mAtm.getLifecycleManager()); + + final Task task = activity.getTask(); + activity.setState(RESUMED, "Testing"); + + // Send a desk UI mode config update. + final Configuration newConfig = new Configuration(task.getConfiguration()); + newConfig.uiMode |= UI_MODE_TYPE_DESK; + task.onRequestedOverrideConfigurationChanged(newConfig); + ensureActivityConfiguration(activity); + + // The activity shouldn't start relaunching since it doesn't have any desk resources. + assertFalse(activity.isRelaunching()); + + // The configuration change is still sent to the activity, even if it doesn't relaunch. + final ActivityConfigurationChangeItem expected = + ActivityConfigurationChangeItem.obtain(newConfig); + verify(mAtm.getLifecycleManager()).scheduleTransaction( + eq(activity.app.getThread()), eq(activity.token), eq(expected)); + } + + @Test + public void testDeskModeChange_relaunchesWithDeskResources() { + mWm.mSkipActivityRelaunchWhenDocking = true; + + final ActivityRecord activity = createActivityWithTask(); + // The activity will already be relaunching out of the gate, finish the relaunch so we can + // test properly. + activity.finishRelaunching(); + + // Activities with desk resources should get relaunched when a UI_MODE_TYPE_DESK change + // comes in. + doReturn(true).when(activity).hasDeskResources(); + + final Task task = activity.getTask(); + activity.setState(RESUMED, "Testing"); + + // Send a desk UI mode config update. + final Configuration newConfig = new Configuration(task.getConfiguration()); + newConfig.uiMode |= UI_MODE_TYPE_DESK; + task.onRequestedOverrideConfigurationChanged(newConfig); + ensureActivityConfiguration(activity); + + // The activity will relaunch since it has desk resources. + assertTrue(activity.isRelaunching()); + } + + @Test public void testSetRequestedOrientationUpdatesConfiguration() throws Exception { final ActivityRecord activity = new ActivityBuilder(mAtm) .setCreateTask(true) |