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

Commit aec5544d authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Reset configuration of invisible size-compat activity if display changes

Assume a size compatibility mode activity is moved to another display,
and then it is put in background. In this case, if the size or density
of display is changed, the existing override configuration (by previous
display) will be reset and also kill its process if the process state
is not important to user (also sync the state criteria to pre-N app).
So when the activity becomes the foreground again, it will have a new
process with the latest configuration.

Bug: 112288258
Fixes: 125587591
Test: atest ActivityRecordTests#testSizeCompatMode_ResetNonVisibleActivity

Change-Id: I14c90b6a7bb00eeb66d2b9f2d8e85ccfa9ef4e51
parent 04164186
Loading
Loading
Loading
Loading
+51 −6
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.wm;

import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityManager.TaskDescription.ATTR_TASKDESCRIPTION_PREFIX;
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
@@ -2726,12 +2727,27 @@ final class ActivityRecord extends ConfigurationContainer {
        }

        final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
        if (parentAppBounds.width() < resolvedAppBounds.width()
                || parentAppBounds.height() < resolvedAppBounds.height()) {
        final int appWidth = resolvedAppBounds.width();
        final int appHeight = resolvedAppBounds.height();
        final int parentAppWidth = parentAppBounds.width();
        final int parentAppHeight = parentAppBounds.height();
        if (parentAppWidth < appWidth || parentAppHeight < appHeight) {
            // One side is larger than the parent.
            return true;
        }

        if (info.hasFixedAspectRatio()) {
            final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
                    / Math.min(appWidth, appHeight);
            final float parentAspectRatio = (0.5f + Math.max(parentAppWidth, parentAppHeight))
                    / Math.min(parentAppWidth, parentAppHeight);
            // Check if the parent still has available space in long side.
            if (aspectRatio < parentAspectRatio
                    && (aspectRatio < info.maxAspectRatio || info.minAspectRatio > 0)) {
                return true;
            }
        }

        final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
        // If the width or height is the same as parent, it is already the best fit of the override
        // bounds, therefore this condition is considered as not size compatibility mode. Here uses
@@ -2930,8 +2946,27 @@ final class ActivityRecord extends ConfigurationContainer {
        }

        final ActivityDisplay display = getDisplay();
        if (display != null) {
        if (display == null) {
            return;
        }
        if (visible) {
            // It may toggle the UI for user to restart the size compatibility mode activity.
            display.handleActivitySizeCompatModeIfNeeded(this);
        } else if (shouldUseSizeCompatMode()) {
            // The override changes can only be obtained from display, because we don't have the
            // difference of full configuration in each hierarchy.
            final int displayChanges = display.getLastOverrideConfigurationChanges();
            final int orientationChanges = CONFIG_WINDOW_CONFIGURATION
                    | CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION;
            final boolean hasNonOrienSizeChanged = hasResizeChange(displayChanges)
                    // Filter out the case of simple orientation change.
                    && (displayChanges & orientationChanges) != orientationChanges;
            // For background activity that uses size compatibility mode, if the size or density of
            // the display is changed, then reset the override configuration and kill the activity's
            // process if its process state is not important to user.
            if (hasNonOrienSizeChanged || (displayChanges & ActivityInfo.CONFIG_DENSITY) != 0) {
                restartProcessIfVisible();
            }
        }
    }

@@ -3398,7 +3433,8 @@ final class ActivityRecord extends ConfigurationContainer {
    void restartProcessIfVisible() {
        Slog.i(TAG, "Request to restart process of " + this);

        // Reset the existing override configuration to the latest configuration.
        // Reset the existing override configuration so it can be updated according to the latest
        // configuration.
        getRequestedOverrideConfiguration().setToDefaults();
        getResolvedOverrideConfiguration().setToDefaults();
        if (visible) {
@@ -3418,8 +3454,17 @@ final class ActivityRecord extends ConfigurationContainer {
            // Kill its process immediately because the activity should be in background.
            // The activity state will be update to {@link #DESTROYED} in
            // {@link ActivityStack#cleanUpActivityLocked} when handling process died.
            mAtmService.mH.post(() -> mAtmService.mAmInternal.killProcess(
                    app.mName, app.mUid, "restartActivityProcess"));
            mAtmService.mH.post(() -> {
                final WindowProcessController wpc;
                synchronized (mAtmService.mGlobalLock) {
                    if (!hasProcess()
                            || app.getReportedProcState() <= PROCESS_STATE_IMPORTANT_FOREGROUND) {
                        return;
                    }
                    wpc = app;
                }
                mAtmService.mAmInternal.killProcess(wpc.mName, wpc.mUid, "resetConfig");
            });
            return;
        }

+1 −1
Original line number Diff line number Diff line
@@ -5209,7 +5209,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
                // the ATMS lock held.
                final Message msg = PooledLambda.obtainMessage(
                        ActivityManagerInternal::killAllBackgroundProcessesExcept, mAmInternal,
                        N, ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
                        N, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
                mH.sendMessage(msg);
            }
        }
+10 −1
Original line number Diff line number Diff line
@@ -77,6 +77,9 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
     */
    private Configuration mFullConfiguration = new Configuration();

    /** The bit mask of the last override fields of full configuration. */
    private int mLastOverrideConfigurationChanges;

    /**
     * Contains merged override configuration settings from the top of the hierarchy down to this
     * particular instance. It is different from {@link #mFullConfiguration} because it starts from
@@ -108,6 +111,11 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
        return mFullConfiguration;
    }

    /** Returns the last changes from applying override configuration. */
    int getLastOverrideConfigurationChanges() {
        return mLastOverrideConfigurationChanges;
    }

    /**
     * Notify that parent config changed and we need to update full configuration.
     * @see #mFullConfiguration
@@ -116,6 +124,7 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
        mTmpConfig.setTo(mResolvedOverrideConfiguration);
        resolveOverrideConfiguration(newParentConfig);
        mFullConfiguration.setTo(newParentConfig);
        mLastOverrideConfigurationChanges =
                mFullConfiguration.updateFrom(mResolvedOverrideConfiguration);
        if (!mTmpConfig.equals(mResolvedOverrideConfiguration)) {
            onMergedOverrideConfigurationChanged();
+57 −16
Original line number Diff line number Diff line
@@ -43,7 +43,10 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;

import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ClientTransaction;
@@ -61,6 +64,8 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;

import java.util.concurrent.TimeUnit;

/**
 * Tests for the {@link ActivityRecord} class.
 *
@@ -175,13 +180,11 @@ public class ActivityRecordTests extends ActivityTestsBase {
    @Test
    public void testRestartProcessIfVisible() {
        doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
        mTask.getWindowConfiguration().setAppBounds(0, 0, 500, 1000);
        mActivity.visible = true;
        mActivity.haveState = false;
        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
        mActivity.info.maxAspectRatio = 1.5f;
        mActivity.setState(ActivityStack.ActivityState.RESUMED, "testRestart");
        ensureActivityConfiguration();
        prepareFixedAspectRatioUnresizableActivity();

        final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
        mTask.getWindowConfiguration().setAppBounds(0, 0, 600, 1200);
        // The visible activity should recompute configuration according to the last parent bounds.
@@ -459,13 +462,9 @@ public class ActivityRecordTests extends ActivityTestsBase {
        newDisplay.getWindowConfiguration().setAppBounds(new Rect(0, 0, 1000, 2000));
        newDisplay.getConfiguration().densityDpi = 300;

        mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds());
        mTask.getConfiguration().densityDpi = 200;
        when(mActivity.mAppWindowToken.getOrientationIgnoreVisibility()).thenReturn(
                ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
        mActivity.info.maxAspectRatio = 1.5f;
        ensureActivityConfiguration();
        prepareFixedAspectRatioUnresizableActivity();

        final Rect originalBounds = new Rect(mActivity.getBounds());
        final int originalDpi = mActivity.getConfiguration().densityDpi;

@@ -475,14 +474,16 @@ public class ActivityRecordTests extends ActivityTestsBase {

        assertEquals(originalBounds, mActivity.getBounds());
        assertEquals(originalDpi, mActivity.getConfiguration().densityDpi);
        assertTrue(mActivity.inSizeCompatMode());
    }

    @Test
    public void testSizeCompatMode_FixedScreenBoundsWhenDisplaySizeChanged() {
        when(mActivity.mAppWindowToken.getOrientationIgnoreVisibility()).thenReturn(
                ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds());
        mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
        mTask.getConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT;
        mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
        ensureActivityConfiguration();
        final Rect originalBounds = new Rect(mActivity.getBounds());
@@ -492,6 +493,7 @@ public class ActivityRecordTests extends ActivityTestsBase {
        ensureActivityConfiguration();

        assertEquals(originalBounds, mActivity.getBounds());
        assertTrue(mActivity.inSizeCompatMode());
    }

    @Test
@@ -500,10 +502,7 @@ public class ActivityRecordTests extends ActivityTestsBase {
                | Configuration.SCREENLAYOUT_SIZE_NORMAL;
        mTask.getConfiguration().screenLayout = fixedScreenLayout
                | Configuration.SCREENLAYOUT_LAYOUTDIR_LTR;
        mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds());
        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
        mActivity.info.maxAspectRatio = 1.5f;
        ensureActivityConfiguration();
        prepareFixedAspectRatioUnresizableActivity();

        // The initial configuration should inherit from parent.
        assertEquals(mTask.getConfiguration().screenLayout,
@@ -517,4 +516,46 @@ public class ActivityRecordTests extends ActivityTestsBase {
        assertEquals(fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_RTL,
                mActivity.getConfiguration().screenLayout);
    }

    @Test
    public void testSizeCompatMode_ResetNonVisibleActivity() {
        final ActivityDisplay display = mStack.getDisplay();
        spyOn(display);

        prepareFixedAspectRatioUnresizableActivity();
        mActivity.setState(STOPPED, "testSizeCompatMode");
        mActivity.visible = false;
        mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
        // Make the parent bounds to be different so the activity is in size compatibility mode.
        mTask.getWindowConfiguration().setAppBounds(new Rect(0, 0, 600, 1200));

        // Simulate the display changes orientation.
        doReturn(ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION
                | ActivityInfo.CONFIG_WINDOW_CONFIGURATION)
                        .when(display).getLastOverrideConfigurationChanges();
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        // The override configuration should not change so it is still in size compatibility mode.
        assertTrue(mActivity.inSizeCompatMode());

        // Simulate the display changes density.
        doReturn(ActivityInfo.CONFIG_DENSITY).when(display).getLastOverrideConfigurationChanges();
        mService.mAmInternal = mock(ActivityManagerInternal.class);
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        // The override configuration should be reset and the activity's process will be killed.
        assertFalse(mActivity.inSizeCompatMode());
        verify(mActivity).restartProcessIfVisible();
        mService.mH.runWithScissors(() -> { }, TimeUnit.SECONDS.toMillis(3));
        verify(mService.mAmInternal).killProcess(
                eq(mActivity.app.mName), eq(mActivity.app.mUid), anyString());
    }

    /** Setup {@link #mActivity} as a size-compat-mode-able activity without fixed orientation. */
    private void prepareFixedAspectRatioUnresizableActivity() {
        when(mActivity.mAppWindowToken.getOrientationIgnoreVisibility()).thenReturn(
                ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds());
        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
        mActivity.info.maxAspectRatio = 1.5f;
        ensureActivityConfiguration();
    }
}