Loading core/java/android/app/ActivityThread.java +61 −2 Original line number Diff line number Diff line Loading @@ -271,6 +271,13 @@ public final class ActivityThread extends ClientTransactionHandler { @UnsupportedAppUsage final H mH = new H(); final Executor mExecutor = new HandlerExecutor(mH); /** * Maps from activity token to local record of running activities in this process. * * This variable is readable if the code is running in activity thread or holding {@link * #mResourcesManager}. It's only writable if the code is running in activity thread and holding * {@link #mResourcesManager}. */ @UnsupportedAppUsage final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); /** The activities to be truly destroyed (not include relaunch). */ Loading Loading @@ -434,6 +441,10 @@ public final class ActivityThread extends ClientTransactionHandler { Configuration newConfig; Configuration createdConfig; Configuration overrideConfig; // Used to save the last reported configuration from server side so that activity // configuration transactions can always use the latest configuration. @GuardedBy("this") private Configuration mPendingOverrideConfig; // Used for consolidating configs before sending on to Activity. private Configuration tmpConfig = new Configuration(); // Callback used for updating activity override config. Loading Loading @@ -3064,7 +3075,12 @@ public final class ActivityThread extends ClientTransactionHandler { } r.setState(ON_CREATE); // updatePendingActivityConfiguration() reads from mActivities to update // ActivityClientRecord which runs in a different thread. Protect modifications to // mActivities to avoid race. synchronized (mResourcesManager) { mActivities.put(r.token, r); } } catch (SuperNotCalledException e) { throw e; Loading Loading @@ -4639,7 +4655,12 @@ public final class ActivityThread extends ClientTransactionHandler { r.setState(ON_DESTROY); } schedulePurgeIdler(); // updatePendingActivityConfiguration() reads from mActivities to update // ActivityClientRecord which runs in a different thread. Protect modifications to // mActivities to avoid race. synchronized (mResourcesManager) { mActivities.remove(token); } StrictMode.decrementExpectedActivityCount(activityClass); return r; } Loading Loading @@ -5382,6 +5403,26 @@ public final class ActivityThread extends ClientTransactionHandler { } } @Override public void updatePendingActivityConfiguration(IBinder activityToken, Configuration overrideConfig) { final ActivityClientRecord r; synchronized (mResourcesManager) { r = mActivities.get(activityToken); } if (r == null) { if (DEBUG_CONFIGURATION) { Slog.w(TAG, "Not found target activity to update its pending config."); } return; } synchronized (r) { r.mPendingOverrideConfig = overrideConfig; } } /** * Handle new activity configuration and/or move to a different display. * @param activityToken Target activity token. Loading @@ -5401,6 +5442,24 @@ public final class ActivityThread extends ClientTransactionHandler { final boolean movedToDifferentDisplay = displayId != INVALID_DISPLAY && displayId != r.activity.getDisplayId(); synchronized (r) { if (r.mPendingOverrideConfig != null && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) { overrideConfig = r.mPendingOverrideConfig; } r.mPendingOverrideConfig = null; } if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig) && !movedToDifferentDisplay) { if (DEBUG_CONFIGURATION) { Slog.v(TAG, "Activity already handled newer configuration so drop this" + " transaction. overrideConfig=" + overrideConfig + " r.overrideConfig=" + r.overrideConfig); } return; } // Perform updates. r.overrideConfig = overrideConfig; final ViewRootImpl viewRoot = r.activity.mDecor != null Loading core/java/android/app/ClientTransactionHandler.java +4 −0 Original line number Diff line number Diff line Loading @@ -124,6 +124,10 @@ public abstract class ClientTransactionHandler { /** Restart the activity after it was stopped. */ public abstract void performRestartActivity(IBinder token, boolean start); /** Set pending activity configuration in case it will be updated by other transaction item. */ public abstract void updatePendingActivityConfiguration(IBinder activityToken, Configuration overrideConfig); /** Deliver activity (override) configuration change. */ public abstract void handleActivityConfigurationChanged(IBinder activityToken, Configuration overrideConfig, int displayId); Loading core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java +5 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,11 @@ public class ActivityConfigurationChangeItem extends ClientTransactionItem { private Configuration mConfiguration; @Override public void preExecute(android.app.ClientTransactionHandler client, IBinder token) { client.updatePendingActivityConfiguration(token, mConfiguration); } @Override public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { Loading core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +228 −2 Original line number Diff line number Diff line Loading @@ -19,28 +19,35 @@ package android.app.activity; import static android.content.Intent.ACTION_EDIT; import static android.content.Intent.ACTION_VIEW; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.app.Activity; import android.app.ActivityThread; import android.app.IApplicationThread; import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ActivityRelaunchItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.StopActivityItem; import android.content.Intent; import android.content.res.Configuration; import android.os.IBinder; import android.support.test.InstrumentationRegistry; import android.support.test.filters.MediumTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.util.MergedConfiguration; import android.view.Display; import org.junit.Test; import org.junit.runner.RunWith; import java.util.concurrent.CountDownLatch; /** * Test for verifying {@link android.app.ActivityThread} class. * Build/Install/Run: Loading @@ -50,8 +57,12 @@ import org.junit.runner.RunWith; @MediumTest public class ActivityThreadTest { private final ActivityTestRule mActivityTestRule = new ActivityTestRule(TestActivity.class, true /* initialTouchMode */, // The first sequence number to try with. Use a large number to avoid conflicts with the first a // few sequence numbers the framework used to launch the test activity. private static final int BASE_SEQ = 10000; private final ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */, false /* launchActivity */); @Test Loading Loading @@ -129,6 +140,179 @@ public class ActivityThreadTest { }); } @Test public void testHandleActivityConfigurationChanged() { final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { final int numOfConfig = activity.mNumOfConfigChanges; applyConfigurationChange(activity, BASE_SEQ); assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); }); } @Test public void testHandleActivityConfigurationChanged_DropStaleConfigurations() { final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // Set the sequence number to BASE_SEQ. applyConfigurationChange(activity, BASE_SEQ); final int orientation = activity.mConfig.orientation; final int numOfConfig = activity.mNumOfConfigChanges; // Try to apply an old configuration change. applyConfigurationChange(activity, BASE_SEQ - 1); assertEquals(numOfConfig, activity.mNumOfConfigChanges); assertEquals(orientation, activity.mConfig.orientation); }); } @Test public void testHandleActivityConfigurationChanged_ApplyNewConfigurations() { final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // Set the sequence number to BASE_SEQ and record the final sequence number it used. final int seq = applyConfigurationChange(activity, BASE_SEQ); final int orientation = activity.mConfig.orientation; final int numOfConfig = activity.mNumOfConfigChanges; // Try to apply an new configuration change. applyConfigurationChange(activity, seq + 1); assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); assertNotEquals(orientation, activity.mConfig.orientation); }); } @Test public void testHandleActivityConfigurationChanged_PickNewerPendingConfiguration() { final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // Set the sequence number to BASE_SEQ and record the final sequence number it used. final int seq = applyConfigurationChange(activity, BASE_SEQ); final int orientation = activity.mConfig.orientation; final int numOfConfig = activity.mNumOfConfigChanges; final ActivityThread activityThread = activity.getActivityThread(); final Configuration pendingConfig = new Configuration(); pendingConfig.orientation = orientation == Configuration.ORIENTATION_LANDSCAPE ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; pendingConfig.seq = seq + 2; activityThread.updatePendingActivityConfiguration(activity.getActivityToken(), pendingConfig); final Configuration newConfig = new Configuration(); newConfig.orientation = orientation; newConfig.seq = seq + 1; activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), newConfig, Display.INVALID_DISPLAY); assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); assertEquals(pendingConfig.orientation, activity.mConfig.orientation); }); } @Test public void testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration() throws Exception { final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); final ActivityThread activityThread = activity.getActivityThread(); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { final Configuration config = new Configuration(); config.seq = BASE_SEQ; config.orientation = Configuration.ORIENTATION_PORTRAIT; activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config, Display.INVALID_DISPLAY); }); final int numOfConfig = activity.mNumOfConfigChanges; final IApplicationThread appThread = activityThread.getApplicationThread(); activity.mConfigLatch = new CountDownLatch(1); activity.mTestLatch = new CountDownLatch(1); Configuration config = new Configuration(); config.seq = BASE_SEQ + 1; config.smallestScreenWidthDp = 100; appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); // Wait until the main thread is performing the configuration change for the configuration // with sequence number BASE_SEQ + 1 before proceeding. This is to mimic the situation where // the activity takes very long time to process configuration changes. activity.mTestLatch.await(); config = new Configuration(); config.seq = BASE_SEQ + 2; config.smallestScreenWidthDp = 200; appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); config = new Configuration(); config.seq = BASE_SEQ + 3; config.smallestScreenWidthDp = 300; appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); config = new Configuration(); config.seq = BASE_SEQ + 4; config.smallestScreenWidthDp = 400; appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); activity.mConfigLatch.countDown(); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); activity.mConfigLatch = null; activity.mTestLatch = null; // Only two more configuration changes: one with seq BASE_SEQ + 1; another with seq // BASE_SEQ + 4. Configurations scheduled in between should be dropped. assertEquals(numOfConfig + 2, activity.mNumOfConfigChanges); assertEquals(400, activity.mConfig.smallestScreenWidthDp); } /** * Calls {@link ActivityThread#handleActivityConfigurationChanged(IBinder, Configuration, int)} * to try to push activity configuration to the activity for the given sequence number. * <p> * It uses orientation to push the configuration and it tries a different orientation if the * first attempt doesn't make through, to rule out the possibility that the previous * configuration already has the same orientation. * * @param activity the test target activity * @param seq the specified sequence number * @return the sequence number this method tried with the last time, so that the caller can use * the next sequence number for next configuration update. */ private int applyConfigurationChange(TestActivity activity, int seq) { final ActivityThread activityThread = activity.getActivityThread(); final int numOfConfig = activity.mNumOfConfigChanges; Configuration config = new Configuration(); config.orientation = Configuration.ORIENTATION_PORTRAIT; config.seq = seq; activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config, Display.INVALID_DISPLAY); if (activity.mNumOfConfigChanges > numOfConfig) { return config.seq; } config = new Configuration(); config.orientation = Configuration.ORIENTATION_LANDSCAPE; config.seq = seq + 1; activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config, Display.INVALID_DISPLAY); return config.seq; } private static ClientTransaction newRelaunchResumeTransaction(Activity activity) { final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null, null, 0, new MergedConfiguration(), false /* preserveWindow */); Loading Loading @@ -162,6 +346,16 @@ public class ActivityThreadTest { return transaction; } private static ClientTransaction newActivityConfigTransaction(Activity activity, Configuration config) { final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config); final ClientTransaction transaction = newTransaction(activity); transaction.addCallback(item); return transaction; } private static ClientTransaction newTransaction(Activity activity) { final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); return ClientTransaction.obtain(appThread, activity.getActivityToken()); Loading @@ -169,5 +363,37 @@ public class ActivityThreadTest { // Test activity public static class TestActivity extends Activity { int mNumOfConfigChanges; final Configuration mConfig = new Configuration(); /** * A latch used to notify tests that we're about to wait for configuration latch. This * is used to notify test code that preExecute phase for activity configuration change * transaction has passed. */ volatile CountDownLatch mTestLatch; /** * If not {@code null} {@link #onConfigurationChanged(Configuration)} won't return until the * latch reaches 0. */ volatile CountDownLatch mConfigLatch; @Override public void onConfigurationChanged(Configuration config) { super.onConfigurationChanged(config); mConfig.setTo(config); ++mNumOfConfigChanges; if (mConfigLatch != null) { if (mTestLatch != null) { mTestLatch.countDown(); } try { mConfigLatch.await(); } catch (InterruptedException e) { throw new IllegalStateException(e); } } } } } services/core/java/com/android/server/wm/ActivityRecord.java +44 −0 Original line number Diff line number Diff line Loading @@ -389,6 +389,11 @@ final class ActivityRecord extends ConfigurationContainer { private boolean mShowWhenLocked; private boolean mTurnScreenOn; /** * Current sequencing integer of the configuration, for skipping old activity configurations. */ private int mConfigurationSeq; /** * Temp configs used in {@link #ensureActivityConfiguration(int, boolean)} */ Loading Loading @@ -2568,6 +2573,45 @@ final class ActivityRecord extends ConfigurationContainer { onRequestedOverrideConfigurationChanged(mTmpConfig); } @Override void resolveOverrideConfiguration(Configuration newParentConfiguration) { super.resolveOverrideConfiguration(newParentConfiguration); // Assign configuration sequence number into hierarchy because there is a different way than // ensureActivityConfiguration() in this class that uses configuration in WindowState during // layout traversals. mConfigurationSeq = Math.max(++mConfigurationSeq, 1); getResolvedOverrideConfiguration().seq = mConfigurationSeq; } @Override public void onConfigurationChanged(Configuration newParentConfig) { super.onConfigurationChanged(newParentConfig); // Configuration's equality doesn't consider seq so if only seq number changes in resolved // override configuration. Therefore ConfigurationContainer doesn't change merged override // configuration, but it's used to push configuration changes so explicitly update that. if (getMergedOverrideConfiguration().seq != getResolvedOverrideConfiguration().seq) { onMergedOverrideConfigurationChanged(); } // TODO(b/80414790): Remove code below after unification. // Same as above it doesn't notify configuration listeners, and consequently AppWindowToken // can't get updated seq number. However WindowState's merged override configuration needs // to have this seq number because that's also used for activity config pushes during layout // traversal. Therefore explicitly update them here. if (mAppWindowToken == null) { return; } final Configuration appWindowTokenRequestedOverrideConfig = mAppWindowToken.getRequestedOverrideConfiguration(); if (appWindowTokenRequestedOverrideConfig.seq != getResolvedOverrideConfiguration().seq) { appWindowTokenRequestedOverrideConfig.seq = getResolvedOverrideConfiguration().seq; mAppWindowToken.onMergedOverrideConfigurationChanged(); } } /** Returns true if the configuration is compatible with this activity. */ boolean isConfigurationCompatible(Configuration config) { final int orientation = mAppWindowToken != null Loading Loading
core/java/android/app/ActivityThread.java +61 −2 Original line number Diff line number Diff line Loading @@ -271,6 +271,13 @@ public final class ActivityThread extends ClientTransactionHandler { @UnsupportedAppUsage final H mH = new H(); final Executor mExecutor = new HandlerExecutor(mH); /** * Maps from activity token to local record of running activities in this process. * * This variable is readable if the code is running in activity thread or holding {@link * #mResourcesManager}. It's only writable if the code is running in activity thread and holding * {@link #mResourcesManager}. */ @UnsupportedAppUsage final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); /** The activities to be truly destroyed (not include relaunch). */ Loading Loading @@ -434,6 +441,10 @@ public final class ActivityThread extends ClientTransactionHandler { Configuration newConfig; Configuration createdConfig; Configuration overrideConfig; // Used to save the last reported configuration from server side so that activity // configuration transactions can always use the latest configuration. @GuardedBy("this") private Configuration mPendingOverrideConfig; // Used for consolidating configs before sending on to Activity. private Configuration tmpConfig = new Configuration(); // Callback used for updating activity override config. Loading Loading @@ -3064,7 +3075,12 @@ public final class ActivityThread extends ClientTransactionHandler { } r.setState(ON_CREATE); // updatePendingActivityConfiguration() reads from mActivities to update // ActivityClientRecord which runs in a different thread. Protect modifications to // mActivities to avoid race. synchronized (mResourcesManager) { mActivities.put(r.token, r); } } catch (SuperNotCalledException e) { throw e; Loading Loading @@ -4639,7 +4655,12 @@ public final class ActivityThread extends ClientTransactionHandler { r.setState(ON_DESTROY); } schedulePurgeIdler(); // updatePendingActivityConfiguration() reads from mActivities to update // ActivityClientRecord which runs in a different thread. Protect modifications to // mActivities to avoid race. synchronized (mResourcesManager) { mActivities.remove(token); } StrictMode.decrementExpectedActivityCount(activityClass); return r; } Loading Loading @@ -5382,6 +5403,26 @@ public final class ActivityThread extends ClientTransactionHandler { } } @Override public void updatePendingActivityConfiguration(IBinder activityToken, Configuration overrideConfig) { final ActivityClientRecord r; synchronized (mResourcesManager) { r = mActivities.get(activityToken); } if (r == null) { if (DEBUG_CONFIGURATION) { Slog.w(TAG, "Not found target activity to update its pending config."); } return; } synchronized (r) { r.mPendingOverrideConfig = overrideConfig; } } /** * Handle new activity configuration and/or move to a different display. * @param activityToken Target activity token. Loading @@ -5401,6 +5442,24 @@ public final class ActivityThread extends ClientTransactionHandler { final boolean movedToDifferentDisplay = displayId != INVALID_DISPLAY && displayId != r.activity.getDisplayId(); synchronized (r) { if (r.mPendingOverrideConfig != null && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) { overrideConfig = r.mPendingOverrideConfig; } r.mPendingOverrideConfig = null; } if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig) && !movedToDifferentDisplay) { if (DEBUG_CONFIGURATION) { Slog.v(TAG, "Activity already handled newer configuration so drop this" + " transaction. overrideConfig=" + overrideConfig + " r.overrideConfig=" + r.overrideConfig); } return; } // Perform updates. r.overrideConfig = overrideConfig; final ViewRootImpl viewRoot = r.activity.mDecor != null Loading
core/java/android/app/ClientTransactionHandler.java +4 −0 Original line number Diff line number Diff line Loading @@ -124,6 +124,10 @@ public abstract class ClientTransactionHandler { /** Restart the activity after it was stopped. */ public abstract void performRestartActivity(IBinder token, boolean start); /** Set pending activity configuration in case it will be updated by other transaction item. */ public abstract void updatePendingActivityConfiguration(IBinder activityToken, Configuration overrideConfig); /** Deliver activity (override) configuration change. */ public abstract void handleActivityConfigurationChanged(IBinder activityToken, Configuration overrideConfig, int displayId); Loading
core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java +5 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,11 @@ public class ActivityConfigurationChangeItem extends ClientTransactionItem { private Configuration mConfiguration; @Override public void preExecute(android.app.ClientTransactionHandler client, IBinder token) { client.updatePendingActivityConfiguration(token, mConfiguration); } @Override public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { Loading
core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +228 −2 Original line number Diff line number Diff line Loading @@ -19,28 +19,35 @@ package android.app.activity; import static android.content.Intent.ACTION_EDIT; import static android.content.Intent.ACTION_VIEW; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.app.Activity; import android.app.ActivityThread; import android.app.IApplicationThread; import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ActivityRelaunchItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.StopActivityItem; import android.content.Intent; import android.content.res.Configuration; import android.os.IBinder; import android.support.test.InstrumentationRegistry; import android.support.test.filters.MediumTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.util.MergedConfiguration; import android.view.Display; import org.junit.Test; import org.junit.runner.RunWith; import java.util.concurrent.CountDownLatch; /** * Test for verifying {@link android.app.ActivityThread} class. * Build/Install/Run: Loading @@ -50,8 +57,12 @@ import org.junit.runner.RunWith; @MediumTest public class ActivityThreadTest { private final ActivityTestRule mActivityTestRule = new ActivityTestRule(TestActivity.class, true /* initialTouchMode */, // The first sequence number to try with. Use a large number to avoid conflicts with the first a // few sequence numbers the framework used to launch the test activity. private static final int BASE_SEQ = 10000; private final ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */, false /* launchActivity */); @Test Loading Loading @@ -129,6 +140,179 @@ public class ActivityThreadTest { }); } @Test public void testHandleActivityConfigurationChanged() { final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { final int numOfConfig = activity.mNumOfConfigChanges; applyConfigurationChange(activity, BASE_SEQ); assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); }); } @Test public void testHandleActivityConfigurationChanged_DropStaleConfigurations() { final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // Set the sequence number to BASE_SEQ. applyConfigurationChange(activity, BASE_SEQ); final int orientation = activity.mConfig.orientation; final int numOfConfig = activity.mNumOfConfigChanges; // Try to apply an old configuration change. applyConfigurationChange(activity, BASE_SEQ - 1); assertEquals(numOfConfig, activity.mNumOfConfigChanges); assertEquals(orientation, activity.mConfig.orientation); }); } @Test public void testHandleActivityConfigurationChanged_ApplyNewConfigurations() { final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // Set the sequence number to BASE_SEQ and record the final sequence number it used. final int seq = applyConfigurationChange(activity, BASE_SEQ); final int orientation = activity.mConfig.orientation; final int numOfConfig = activity.mNumOfConfigChanges; // Try to apply an new configuration change. applyConfigurationChange(activity, seq + 1); assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); assertNotEquals(orientation, activity.mConfig.orientation); }); } @Test public void testHandleActivityConfigurationChanged_PickNewerPendingConfiguration() { final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // Set the sequence number to BASE_SEQ and record the final sequence number it used. final int seq = applyConfigurationChange(activity, BASE_SEQ); final int orientation = activity.mConfig.orientation; final int numOfConfig = activity.mNumOfConfigChanges; final ActivityThread activityThread = activity.getActivityThread(); final Configuration pendingConfig = new Configuration(); pendingConfig.orientation = orientation == Configuration.ORIENTATION_LANDSCAPE ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; pendingConfig.seq = seq + 2; activityThread.updatePendingActivityConfiguration(activity.getActivityToken(), pendingConfig); final Configuration newConfig = new Configuration(); newConfig.orientation = orientation; newConfig.seq = seq + 1; activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), newConfig, Display.INVALID_DISPLAY); assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); assertEquals(pendingConfig.orientation, activity.mConfig.orientation); }); } @Test public void testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration() throws Exception { final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); final ActivityThread activityThread = activity.getActivityThread(); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { final Configuration config = new Configuration(); config.seq = BASE_SEQ; config.orientation = Configuration.ORIENTATION_PORTRAIT; activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config, Display.INVALID_DISPLAY); }); final int numOfConfig = activity.mNumOfConfigChanges; final IApplicationThread appThread = activityThread.getApplicationThread(); activity.mConfigLatch = new CountDownLatch(1); activity.mTestLatch = new CountDownLatch(1); Configuration config = new Configuration(); config.seq = BASE_SEQ + 1; config.smallestScreenWidthDp = 100; appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); // Wait until the main thread is performing the configuration change for the configuration // with sequence number BASE_SEQ + 1 before proceeding. This is to mimic the situation where // the activity takes very long time to process configuration changes. activity.mTestLatch.await(); config = new Configuration(); config.seq = BASE_SEQ + 2; config.smallestScreenWidthDp = 200; appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); config = new Configuration(); config.seq = BASE_SEQ + 3; config.smallestScreenWidthDp = 300; appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); config = new Configuration(); config.seq = BASE_SEQ + 4; config.smallestScreenWidthDp = 400; appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); activity.mConfigLatch.countDown(); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); activity.mConfigLatch = null; activity.mTestLatch = null; // Only two more configuration changes: one with seq BASE_SEQ + 1; another with seq // BASE_SEQ + 4. Configurations scheduled in between should be dropped. assertEquals(numOfConfig + 2, activity.mNumOfConfigChanges); assertEquals(400, activity.mConfig.smallestScreenWidthDp); } /** * Calls {@link ActivityThread#handleActivityConfigurationChanged(IBinder, Configuration, int)} * to try to push activity configuration to the activity for the given sequence number. * <p> * It uses orientation to push the configuration and it tries a different orientation if the * first attempt doesn't make through, to rule out the possibility that the previous * configuration already has the same orientation. * * @param activity the test target activity * @param seq the specified sequence number * @return the sequence number this method tried with the last time, so that the caller can use * the next sequence number for next configuration update. */ private int applyConfigurationChange(TestActivity activity, int seq) { final ActivityThread activityThread = activity.getActivityThread(); final int numOfConfig = activity.mNumOfConfigChanges; Configuration config = new Configuration(); config.orientation = Configuration.ORIENTATION_PORTRAIT; config.seq = seq; activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config, Display.INVALID_DISPLAY); if (activity.mNumOfConfigChanges > numOfConfig) { return config.seq; } config = new Configuration(); config.orientation = Configuration.ORIENTATION_LANDSCAPE; config.seq = seq + 1; activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config, Display.INVALID_DISPLAY); return config.seq; } private static ClientTransaction newRelaunchResumeTransaction(Activity activity) { final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null, null, 0, new MergedConfiguration(), false /* preserveWindow */); Loading Loading @@ -162,6 +346,16 @@ public class ActivityThreadTest { return transaction; } private static ClientTransaction newActivityConfigTransaction(Activity activity, Configuration config) { final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config); final ClientTransaction transaction = newTransaction(activity); transaction.addCallback(item); return transaction; } private static ClientTransaction newTransaction(Activity activity) { final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); return ClientTransaction.obtain(appThread, activity.getActivityToken()); Loading @@ -169,5 +363,37 @@ public class ActivityThreadTest { // Test activity public static class TestActivity extends Activity { int mNumOfConfigChanges; final Configuration mConfig = new Configuration(); /** * A latch used to notify tests that we're about to wait for configuration latch. This * is used to notify test code that preExecute phase for activity configuration change * transaction has passed. */ volatile CountDownLatch mTestLatch; /** * If not {@code null} {@link #onConfigurationChanged(Configuration)} won't return until the * latch reaches 0. */ volatile CountDownLatch mConfigLatch; @Override public void onConfigurationChanged(Configuration config) { super.onConfigurationChanged(config); mConfig.setTo(config); ++mNumOfConfigChanges; if (mConfigLatch != null) { if (mTestLatch != null) { mTestLatch.countDown(); } try { mConfigLatch.await(); } catch (InterruptedException e) { throw new IllegalStateException(e); } } } } }
services/core/java/com/android/server/wm/ActivityRecord.java +44 −0 Original line number Diff line number Diff line Loading @@ -389,6 +389,11 @@ final class ActivityRecord extends ConfigurationContainer { private boolean mShowWhenLocked; private boolean mTurnScreenOn; /** * Current sequencing integer of the configuration, for skipping old activity configurations. */ private int mConfigurationSeq; /** * Temp configs used in {@link #ensureActivityConfiguration(int, boolean)} */ Loading Loading @@ -2568,6 +2573,45 @@ final class ActivityRecord extends ConfigurationContainer { onRequestedOverrideConfigurationChanged(mTmpConfig); } @Override void resolveOverrideConfiguration(Configuration newParentConfiguration) { super.resolveOverrideConfiguration(newParentConfiguration); // Assign configuration sequence number into hierarchy because there is a different way than // ensureActivityConfiguration() in this class that uses configuration in WindowState during // layout traversals. mConfigurationSeq = Math.max(++mConfigurationSeq, 1); getResolvedOverrideConfiguration().seq = mConfigurationSeq; } @Override public void onConfigurationChanged(Configuration newParentConfig) { super.onConfigurationChanged(newParentConfig); // Configuration's equality doesn't consider seq so if only seq number changes in resolved // override configuration. Therefore ConfigurationContainer doesn't change merged override // configuration, but it's used to push configuration changes so explicitly update that. if (getMergedOverrideConfiguration().seq != getResolvedOverrideConfiguration().seq) { onMergedOverrideConfigurationChanged(); } // TODO(b/80414790): Remove code below after unification. // Same as above it doesn't notify configuration listeners, and consequently AppWindowToken // can't get updated seq number. However WindowState's merged override configuration needs // to have this seq number because that's also used for activity config pushes during layout // traversal. Therefore explicitly update them here. if (mAppWindowToken == null) { return; } final Configuration appWindowTokenRequestedOverrideConfig = mAppWindowToken.getRequestedOverrideConfiguration(); if (appWindowTokenRequestedOverrideConfig.seq != getResolvedOverrideConfiguration().seq) { appWindowTokenRequestedOverrideConfig.seq = getResolvedOverrideConfiguration().seq; mAppWindowToken.onMergedOverrideConfigurationChanged(); } } /** Returns true if the configuration is compatible with this activity. */ boolean isConfigurationCompatible(Configuration config) { final int orientation = mAppWindowToken != null Loading