From ceef7461c2b082d450040b22872e691424095fc6 Mon Sep 17 00:00:00 2001 From: Andrii Kulian Date: Wed, 1 May 2019 18:58:28 -0700 Subject: [PATCH] Override process config for cold start on secondary screens Some apps use Application context to get display configuration and metrics for their activities. This leads to incorrect configuration used for activities on secondary screens. This is an issue on the application side, but the framework can provide some compatibility - override process-level config to match the display where the first activity was launched. Once an activity in the process is launched on the default display, stop watching other displays config to preserve compatibility. Bug: 131179060 Test: atest WmTests:ActivityRecordTests#testSetProcessOverridesConfig Test: Launch GMail on secondary screen Change-Id: If76f4a83c708f47fd914da68dd4b7366ffd8b88c --- .../com/android/server/wm/ActivityRecord.java | 28 +++++++- .../server/wm/ActivityRecordTests.java | 66 ++++++++++++++++++- .../android/server/wm/ActivityTestsBase.java | 22 +++++-- .../server/wm/AppWindowTokenTests.java | 2 - .../server/wm/SystemServicesTestRule.java | 7 ++ .../android/server/wm/WindowTestsBase.java | 1 - 6 files changed, 115 insertions(+), 11 deletions(-) diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 1344727ab36da..66b305ec2dac5 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1085,6 +1085,22 @@ final class ActivityRecord extends ConfigurationContainer { if (root == this) { task.setRootProcess(proc); } + // Override the process configuration to match the display where the first activity in + // the process was launched. This can help with compat issues on secondary displays when + // apps use Application to obtain configuration or metrics instead of Activity. + final ActivityDisplay display = getDisplay(); + if (display == null || display.mDisplayId == INVALID_DISPLAY) { + return; + } + if (!proc.hasActivities() && display.mDisplayId != DEFAULT_DISPLAY) { + proc.registerDisplayConfigurationListenerLocked(display); + } else if (display.mDisplayId == DEFAULT_DISPLAY) { + // Once an activity is launched on default display - stop listening for other displays + // configurations to maintain compatibility with previous platform releases. E.g. when + // an activity is launched in a Bubble and then moved to default screen, we should match + // the global device config. + proc.unregisterDisplayConfigurationListenerLocked(); + } } boolean hasProcess() { @@ -3233,7 +3249,7 @@ final class ActivityRecord extends ConfigurationContainer { // Update last reported values. final Configuration newMergedOverrideConfig = getMergedOverrideConfiguration(); - setLastReportedConfiguration(mAtmService.getGlobalConfiguration(), newMergedOverrideConfig); + setLastReportedConfiguration(getProcessGlobalConfiguration(), newMergedOverrideConfig); if (mState == INITIALIZING) { // No need to relaunch or schedule new config for activity that hasn't been launched @@ -3342,6 +3358,14 @@ final class ActivityRecord extends ConfigurationContainer { return true; } + /** Get process configuration, or global config if the process is not set. */ + private Configuration getProcessGlobalConfiguration() { + if (app != null) { + return app.getConfiguration(); + } + return mAtmService.getGlobalConfiguration(); + } + /** * When assessing a configuration change, decide if the changes flags and the new configurations * should cause the Activity to relaunch. @@ -3449,7 +3473,7 @@ final class ActivityRecord extends ConfigurationContainer { mStackSupervisor.activityRelaunchingLocked(this); final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(pendingResults, pendingNewIntents, configChangeFlags, - new MergedConfiguration(mAtmService.getGlobalConfiguration(), + new MergedConfiguration(getProcessGlobalConfiguration(), getMergedOverrideConfiguration()), preserveWindow); final ActivityLifecycleItem lifecycleItem; 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 11a177a71a10a..f8fd64a8feb2c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_90; @@ -38,6 +40,7 @@ import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING; import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_INVISIBLE; import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE; import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; +import static com.android.server.wm.WindowContainer.POSITION_TOP; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -56,10 +59,10 @@ import android.app.servertransaction.PauseActivityItem; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; -import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.util.MergedConfiguration; import android.util.MutableBoolean; +import android.view.DisplayInfo; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner.Stub; import android.view.RemoteAnimationAdapter; @@ -598,6 +601,67 @@ public class ActivityRecordTests extends ActivityTestsBase { assertNull(mActivity.pendingOptions); } + @Test + public void testSetProcessOverridesConfig() { + final ActivityRecord defaultDisplayActivity = + createActivityOnDisplay(true /* defaultDisplay */, null /* process */); + assertFalse(defaultDisplayActivity.app.registeredForDisplayConfigChanges()); + + final ActivityRecord secondaryDisplayActivity = + createActivityOnDisplay(false /* defaultDisplay */, null /* process */); + assertTrue(secondaryDisplayActivity.app.registeredForDisplayConfigChanges()); + assertEquals(secondaryDisplayActivity.getDisplay().getResolvedOverrideConfiguration(), + secondaryDisplayActivity.app.getRequestedOverrideConfiguration()); + + assertNotEquals(defaultDisplayActivity.getConfiguration(), + secondaryDisplayActivity.getConfiguration()); + } + + @Test + public void testSetProcessDoesntOverrideConfigIfAnotherActivityPresent() { + final ActivityRecord defaultDisplayActivity = + createActivityOnDisplay(true /* defaultDisplay */, null /* process */); + assertFalse(defaultDisplayActivity.app.registeredForDisplayConfigChanges()); + + final ActivityRecord secondaryDisplayActivity = + createActivityOnDisplay(false /* defaultDisplay */, defaultDisplayActivity.app); + assertFalse(secondaryDisplayActivity.app.registeredForDisplayConfigChanges()); + } + + @Test + public void testActivityOnDefaultDisplayClearsProcessOverride() { + final ActivityRecord secondaryDisplayActivity = + createActivityOnDisplay(false /* defaultDisplay */, null /* process */); + assertTrue(secondaryDisplayActivity.app.registeredForDisplayConfigChanges()); + + final ActivityRecord defaultDisplayActivity = + createActivityOnDisplay(true /* defaultDisplay */, + secondaryDisplayActivity.app); + assertFalse(defaultDisplayActivity.app.registeredForDisplayConfigChanges()); + assertFalse(secondaryDisplayActivity.app.registeredForDisplayConfigChanges()); + } + + /** + * Creates an activity on display. For non-default display request it will also create a new + * display with custom DisplayInfo. + */ + private ActivityRecord createActivityOnDisplay(boolean defaultDisplay, + WindowProcessController process) { + final ActivityDisplay display; + if (defaultDisplay) { + display = mRootActivityContainer.getDefaultDisplay(); + } else { + final DisplayInfo info = new DisplayInfo(); + info.logicalWidth = 100; + info.logicalHeight = 100; + display = addNewActivityDisplayAt(info, POSITION_TOP); + } + final TestActivityStack stack = display.createStack(WINDOWING_MODE_UNDEFINED, + ACTIVITY_TYPE_STANDARD, true /* onTop */); + final TaskRecord task = new TaskBuilder(mSupervisor).setStack(stack).build(); + return new ActivityBuilder(mService).setTask(task).setUseProcess(process).build(); + } + /** Setup {@link #mActivity} as a size-compat-mode-able activity without fixed orientation. */ private void prepareFixedAspectRatioUnresizableActivity() { setupDisplayContentForCompatDisplayInsets(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index 53b0add8c37ee..d8c0de7418457 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -195,6 +195,7 @@ class ActivityTestsBase { private ActivityStack mStack; private int mActivityFlags; private int mLaunchMode; + private WindowProcessController mWpc; ActivityBuilder(ActivityTaskManagerService service) { mService = service; @@ -245,6 +246,11 @@ class ActivityTestsBase { return this; } + ActivityBuilder setUseProcess(WindowProcessController wpc) { + mWpc = wpc; + return this; + } + ActivityRecord build() { if (mComponent == null) { final int id = sCurrentActivityId++; @@ -290,12 +296,18 @@ class ActivityTestsBase { mTaskRecord.addActivityToTop(activity); } - final WindowProcessController wpc = new WindowProcessController(mService, - mService.mContext.getApplicationInfo(), "name", 12345, - UserHandle.getUserId(12345), mock(Object.class), - mock(WindowProcessListener.class)); - wpc.setThread(mock(IApplicationThread.class)); + final WindowProcessController wpc; + if (mWpc != null) { + wpc = mWpc; + } else { + wpc = new WindowProcessController(mService, + mService.mContext.getApplicationInfo(), "name", 12345, + UserHandle.getUserId(12345), mock(Object.class), + mock(WindowProcessListener.class)); + wpc.setThread(mock(IApplicationThread.class)); + } activity.setProcess(wpc); + wpc.addActivityIfNeeded(activity); return activity; } } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java index fe45411830381..2c9074e933b65 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java @@ -153,7 +153,6 @@ public class AppWindowTokenTests extends WindowTestsBase { @FlakyTest(bugId = 131005232) public void testLandscapeSeascapeRotationByApp() { // Some plumbing to get the service ready for rotation updates. - mWm.mDisplayReady = true; mWm.mDisplayEnabled = true; final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams( @@ -186,7 +185,6 @@ public class AppWindowTokenTests extends WindowTestsBase { @Test public void testLandscapeSeascapeRotationByPolicy() { // Some plumbing to get the service ready for rotation updates. - mWm.mDisplayReady = true; mWm.mDisplayEnabled = true; final DisplayRotation spiedRotation = spy(mDisplayContent.getDisplayRotation()); diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 366aceafd7bf4..427a92963807e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -41,6 +41,7 @@ import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.IntentFilter; +import android.content.res.Configuration; import android.database.ContentObserver; import android.hardware.display.DisplayManagerInternal; import android.net.Uri; @@ -175,6 +176,12 @@ public class SystemServicesTestRule implements TestRule { // Display creation is driven by the ActivityManagerService via // ActivityStackSupervisor. We emulate those steps here. mWindowManagerService.mRoot.createDisplayContent(display, mock(ActivityDisplay.class)); + mWindowManagerService.displayReady(); + + final Configuration defaultDisplayConfig = + mWindowManagerService.computeNewConfiguration(DEFAULT_DISPLAY); + doReturn(defaultDisplayConfig).when(atms).getGlobalConfiguration(); + doReturn(defaultDisplayConfig).when(atms).getGlobalConfigurationForPid(anyInt()); mMockTracker.stopTracking(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 3a702cb9521ca..de28b5f0fa4ff 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -154,7 +154,6 @@ class WindowTestsBase { context.getDisplay().getDisplayInfo(mDisplayInfo); mDisplayContent = createNewDisplay(); mWm.mDisplayEnabled = true; - mWm.mDisplayReady = true; // Set-up some common windows. mCommonWindows = new HashSet<>();