Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0c81667a authored by Garfield Tan's avatar Garfield Tan Committed by Android (Google) Code Review
Browse files

Merge "Drop stale activity configuration updates."

parents a8f68e24 0443b37c
Loading
Loading
Loading
Loading
+61 −2
Original line number Diff line number Diff line
@@ -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). */
@@ -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.
@@ -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;
@@ -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;
    }
@@ -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.
@@ -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
+4 −0
Original line number Diff line number Diff line
@@ -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);
+5 −0
Original line number Diff line number Diff line
@@ -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) {
+228 −2
Original line number Diff line number Diff line
@@ -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:
@@ -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
@@ -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 */);
@@ -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());
@@ -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);
                }
            }
        }
    }
}
+44 −0
Original line number Diff line number Diff line
@@ -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)}
     */
@@ -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