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

Commit 49c6eb47 authored by Evan Rosky's avatar Evan Rosky Committed by Android (Google) Code Review
Browse files

Merge "Add FULLSCREEN policies to task level"

parents b91b3b15 730f6e89
Loading
Loading
Loading
Loading
+10 −7
Original line number Diff line number Diff line
@@ -728,9 +728,10 @@ final class ActivityRecord extends ConfigurationContainer {
            mLastReportedMultiWindowMode = inPictureInPictureMode;
            final Configuration newConfig = new Configuration();
            if (targetStackBounds != null && !targetStackBounds.isEmpty()) {
                task.computeResolvedOverrideConfiguration(newConfig,
                        task.getParent().getConfiguration(),
                        task.getRequestedOverrideConfiguration());
                newConfig.setTo(task.getRequestedOverrideConfiguration());
                Rect outBounds = newConfig.windowConfiguration.getBounds();
                task.adjustForMinimalTaskDimensions(outBounds, outBounds);
                task.computeConfigResourceOverrides(newConfig, task.getParent().getConfiguration());
            }
            schedulePictureInPictureModeChanged(newConfig);
            scheduleMultiWindowModeChanged(newConfig);
@@ -2503,7 +2504,8 @@ final class ActivityRecord extends ConfigurationContainer {
            return;
        }

        final IBinder binder = freezeScreenIfNeeded ? appToken.asBinder() : null;
        final IBinder binder =
                (freezeScreenIfNeeded && appToken != null) ? appToken.asBinder() : null;
        mAppWindowToken.setOrientation(requestedOrientation, binder, this);
    }

@@ -2547,7 +2549,6 @@ final class ActivityRecord extends ConfigurationContainer {

    // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
    private void updateOverrideConfiguration() {
        mTmpConfig.unset();
        computeBounds(mTmpBounds);

        if (mTmpBounds.equals(getRequestedOverrideBounds())) {
@@ -2558,8 +2559,10 @@ final class ActivityRecord extends ConfigurationContainer {

        // Bounds changed...update configuration to match.
        if (!matchParentBounds()) {
            task.computeResolvedOverrideConfiguration(mTmpConfig,
                    task.getParent().getConfiguration(), getRequestedOverrideConfiguration());
            mTmpConfig.setTo(getRequestedOverrideConfiguration());
            task.computeConfigResourceOverrides(mTmpConfig, task.getParent().getConfiguration());
        } else {
            mTmpConfig.unset();
        }

        onRequestedOverrideConfigurationChanged(mTmpConfig);
+6 −0
Original line number Diff line number Diff line
@@ -3656,6 +3656,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
        }
    }

    /** @returns the orientation of the display when it's rotation is ROTATION_0. */
    int getNaturalOrientation() {
        return mBaseDisplayWidth < mBaseDisplayHeight
                ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
    }

    void performLayout(boolean initial, boolean updateInputWindows) {
        if (!isLayoutNeeded()) {
            return;
+99 −34
Original line number Diff line number Diff line
@@ -44,6 +44,9 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
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.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -1259,10 +1262,6 @@ class TaskRecord extends ConfigurationContainer {
        setFrontOfTask();
    }

    void addActivityAtBottom(ActivityRecord r) {
        addActivityAtIndex(0, r);
    }

    void addActivityToTop(ActivityRecord r) {
        addActivityAtIndex(mActivities.size(), r);
    }
@@ -1277,6 +1276,34 @@ class TaskRecord extends ConfigurationContainer {
        return mActivities.get(0).getActivityType();
    }

    /**
     * Checks if the root activity requires a particular orientation (either by override or
     * activityInfo) and returns that. Otherwise, this returns ORIENTATION_UNDEFINED.
     */
    private int getRootActivityRequestedOrientation() {
        ActivityRecord root = getRootActivity();
        if (getRequestedOverrideConfiguration().orientation != ORIENTATION_UNDEFINED
                || root == null) {
            return getRequestedOverrideConfiguration().orientation;
        }
        int rootScreenOrientation = root.getOrientation();
        if (rootScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
            // NOSENSOR means the display's "natural" orientation, so return that.
            ActivityDisplay display = mStack != null ? mStack.getDisplay() : null;
            if (display != null && display.mDisplayContent != null) {
                return mStack.getDisplay().mDisplayContent.getNaturalOrientation();
            }
        } else if (rootScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
            // LOCKED means the activity's orientation remains unchanged, so return existing value.
            return root.getConfiguration().orientation;
        } else if (ActivityInfo.isFixedOrientationLandscape(rootScreenOrientation)) {
            return ORIENTATION_LANDSCAPE;
        } else if (ActivityInfo.isFixedOrientationPortrait(rootScreenOrientation)) {
            return ORIENTATION_PORTRAIT;
        }
        return ORIENTATION_UNDEFINED;
    }

    /**
     * Adds an activity {@param r} at the given {@param index}. The activity {@param r} must either
     * be in the current task or unparented to any task.
@@ -1741,7 +1768,7 @@ class TaskRecord extends ConfigurationContainer {
        updateTaskDescription();
    }

    private void adjustForMinimalTaskDimensions(Rect bounds, Rect previousBounds) {
    void adjustForMinimalTaskDimensions(Rect bounds, Rect previousBounds) {
        if (bounds == null) {
            return;
        }
@@ -1853,11 +1880,27 @@ class TaskRecord extends ConfigurationContainer {

    @Override
    public void onConfigurationChanged(Configuration newParentConfig) {
        // Check if the new configuration supports persistent bounds (eg. is Freeform) and if so
        // restore the last recorded non-fullscreen bounds.
        final boolean prevPersistTaskBounds = getWindowConfiguration().persistTaskBounds();
        final boolean nextPersistTaskBounds =
                getRequestedOverrideConfiguration().windowConfiguration.persistTaskBounds()
                || newParentConfig.windowConfiguration.persistTaskBounds();
        if (!prevPersistTaskBounds && nextPersistTaskBounds
                && mLastNonFullscreenBounds != null && !mLastNonFullscreenBounds.isEmpty()) {
            // Bypass onRequestedOverrideConfigurationChanged here to avoid infinite loop.
            getRequestedOverrideConfiguration().windowConfiguration
                    .setBounds(mLastNonFullscreenBounds);
        }

        final boolean wasInMultiWindowMode = inMultiWindowMode();
        super.onConfigurationChanged(newParentConfig);
        if (wasInMultiWindowMode != inMultiWindowMode()) {
            mService.mStackSupervisor.scheduleUpdateMultiWindowMode(this);
        }

        // If the configuration supports persistent bounds (eg. Freeform), keep track of the
        // current (non-fullscreen) bounds for persistence.
        if (getWindowConfiguration().persistTaskBounds()) {
            final Rect currentBounds = getRequestedOverrideBounds();
            if (!currentBounds.isEmpty()) {
@@ -2047,7 +2090,7 @@ class TaskRecord extends ConfigurationContainer {
     * configuring an "inherit-bounds" window which means that all configuration settings would
     * just be inherited from the parent configuration.
     **/
    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @NonNull Rect bounds,
    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
            @NonNull Configuration parentConfig) {
        int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
@@ -2060,6 +2103,7 @@ class TaskRecord extends ConfigurationContainer {
        }
        density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;

        final Rect bounds = inOutConfig.windowConfiguration.getBounds();
        Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
        if (outAppBounds == null || outAppBounds.isEmpty()) {
            inOutConfig.windowConfiguration.setAppBounds(bounds);
@@ -2107,13 +2151,14 @@ class TaskRecord extends ConfigurationContainer {
                    // Iterating across all screen orientations, and return the minimum of the task
                    // width taking into account that the bounds might change because the snap
                    // algorithm snaps to a different value
                    inOutConfig.smallestScreenWidthDp =
                            getSmallestScreenWidthDpForDockedBounds(bounds);
                }
                // otherwise, it will just inherit
            }
        }

        if (inOutConfig.orientation == Configuration.ORIENTATION_UNDEFINED) {
        if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
            inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
                    ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
        }
@@ -2134,36 +2179,56 @@ class TaskRecord extends ConfigurationContainer {
        }
    }

    // TODO(b/113900640): remove this once ActivityRecord is changed to not need it anymore.
    void computeResolvedOverrideConfiguration(Configuration inOutConfig, Configuration parentConfig,
            Configuration overrideConfig) {
        // Save previous bounds because adjustForMinimalTaskDimensions uses that to determine if it
        // changes left bound vs. right bound, or top bound vs. bottom bound.
        mTmpBounds.set(inOutConfig.windowConfiguration.getBounds());

        inOutConfig.setTo(overrideConfig);

        Rect outOverrideBounds = inOutConfig.windowConfiguration.getBounds();
        if (outOverrideBounds != null && !outOverrideBounds.isEmpty()) {
            adjustForMinimalTaskDimensions(outOverrideBounds, mTmpBounds);

            int windowingMode = overrideConfig.windowConfiguration.getWindowingMode();
    @Override
    void resolveOverrideConfiguration(Configuration newParentConfig) {
        mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
        super.resolveOverrideConfiguration(newParentConfig);
        int windowingMode =
                getRequestedOverrideConfiguration().windowConfiguration.getWindowingMode();
        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
                windowingMode = parentConfig.windowConfiguration.getWindowingMode();
            windowingMode = newParentConfig.windowConfiguration.getWindowingMode();
        }
        Rect outOverrideBounds =
                getResolvedOverrideConfiguration().windowConfiguration.getBounds();

        if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
            // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent"
            outOverrideBounds.setEmpty();

            // If the task or its root activity require a different orientation, make it fit the
            // available bounds by scaling down its bounds.
            int forcedOrientation = getRootActivityRequestedOrientation();
            if (forcedOrientation != ORIENTATION_UNDEFINED
                    && forcedOrientation != newParentConfig.orientation) {
                final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
                final int parentWidth = parentBounds.width();
                final int parentHeight = parentBounds.height();
                final float aspect = ((float) parentHeight) / parentWidth;
                if (forcedOrientation == ORIENTATION_LANDSCAPE) {
                    final int height = (int) (parentWidth / aspect);
                    final int top = parentBounds.centerY() - height / 2;
                    outOverrideBounds.set(
                            parentBounds.left, top, parentBounds.right, top + height);
                } else {
                    final int width = (int) (parentHeight * aspect);
                    final int left = parentBounds.centerX() - width / 2;
                    outOverrideBounds.set(
                            left, parentBounds.top, left + width, parentBounds.bottom);
                }
            if (windowingMode == WINDOWING_MODE_FREEFORM) {
                // by policy, make sure the window remains within parent
                fitWithinBounds(outOverrideBounds, parentConfig.windowConfiguration.getBounds());
            }

            computeConfigResourceOverrides(inOutConfig, outOverrideBounds, parentConfig);
        }

        if (outOverrideBounds.isEmpty()) {
            // If the task fills the parent, just inherit all the other configs from parent.
            return;
        }

    @Override
    void resolveOverrideConfiguration(Configuration newParentConfig) {
        computeResolvedOverrideConfiguration(getResolvedOverrideConfiguration(), newParentConfig,
                getRequestedOverrideConfiguration());
        adjustForMinimalTaskDimensions(outOverrideBounds, mTmpBounds);
        if (windowingMode == WINDOWING_MODE_FREEFORM) {
            // by policy, make sure the window remains within parent somewhere
            fitWithinBounds(outOverrideBounds, newParentConfig.windowConfiguration.getBounds());
        }
        computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
    }

    Rect updateOverrideConfigurationFromLaunchBounds() {
+20 −1
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -38,6 +39,9 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doCallRealMethod;

import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.IApplicationThread;
@@ -144,6 +148,13 @@ class ActivityTestsBase {
        return display;
    }

    /** Creates and adds a {@link TestActivityDisplay} to supervisor at the given position. */
    TestActivityDisplay addNewActivityDisplayAt(DisplayInfo info, int position) {
        final TestActivityDisplay display = createNewActivityDisplay(info);
        mRootActivityContainer.addChild(display, position);
        return display;
    }

    /**
     * Builder for creating new activities.
     */
@@ -234,6 +245,10 @@ class ActivityTestsBase {
                    mService.mStackSupervisor, null /* options */, null /* sourceRecord */);
            spyOn(activity);
            activity.mAppWindowToken = mock(AppWindowToken.class);
            doCallRealMethod().when(activity.mAppWindowToken).getOrientationIgnoreVisibility();
            doCallRealMethod().when(activity.mAppWindowToken)
                    .setOrientation(anyInt(), any(), any());
            doCallRealMethod().when(activity.mAppWindowToken).setOrientation(anyInt());
            doNothing().when(activity).removeWindowContainer();

            if (mTaskRecord != null) {
@@ -346,6 +361,7 @@ class ActivityTestsBase {
                mStack.addTask(task, true, "creating test task");
                task.setStack(mStack);
                task.setTask();
                mStack.getWindowContainerController().mContainer.addChild(task.mTask, 0);
            }

            task.touchActiveTime();
@@ -365,7 +381,10 @@ class ActivityTestsBase {
                setTask();
            }

            private void setTask() {
            void setTask() {
                Task mockTask = mock(Task.class);
                mockTask.mTaskRecord = this;
                doCallRealMethod().when(mockTask).onDescendantOrientationChanged(any(), any());
                setTask(mock(Task.class));
            }
        }
+100 −16
Original line number Diff line number Diff line
@@ -21,6 +21,11 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;

import static com.android.server.wm.WindowContainer.POSITION_TOP;

import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.sameInstance;
@@ -133,17 +138,6 @@ public class TaskRecordTests extends ActivityTestsBase {
        assertTrue(task.returnsToHomeStack());
    }

    /** Ensures that bounds are clipped to their parent. */
    @Test
    public void testAppBounds_BoundsClipping() {
        final Rect shiftedBounds = new Rect(mParentBounds);
        shiftedBounds.offset(10, 10);
        final Rect expectedBounds = new Rect(mParentBounds);
        expectedBounds.intersect(shiftedBounds);
        testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, shiftedBounds,
                expectedBounds);
    }

    /** Ensures that empty bounds are not propagated to the configuration. */
    @Test
    public void testAppBounds_EmptyBounds() {
@@ -167,18 +161,108 @@ public class TaskRecordTests extends ActivityTestsBase {
        final Rect insetBounds = new Rect(mParentBounds);
        insetBounds.inset(5, 5, 5, 5);
        testStackBoundsConfiguration(
                WINDOWING_MODE_FULLSCREEN, mParentBounds, insetBounds, insetBounds);
                WINDOWING_MODE_FREEFORM, mParentBounds, insetBounds, insetBounds);
    }

    /** Ensures that full screen free form bounds are clipped */
    /** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */
    @Test
    public void testAppBounds_FullScreenFreeFormBounds() {
    public void testBoundsOnModeChangeFreeformToFullscreen() {
        ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
        ActivityStack stack = new StackBuilder(mRootActivityContainer).setDisplay(display)
                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
        TaskRecord task = stack.getChildAt(0);
        task.getRootActivity().mAppWindowToken.setOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
        DisplayInfo info = new DisplayInfo();
        display.mDisplay.getDisplayInfo(info);
        final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight);
        testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, fullScreenBounds,
                mParentBounds);
        final Rect freeformBounds = new Rect(fullScreenBounds);
        freeformBounds.inset((int) (freeformBounds.width() * 0.2),
                (int) (freeformBounds.height() * 0.2));
        task.setBounds(freeformBounds);

        assertEquals(freeformBounds, task.getBounds());

        // FULLSCREEN inherits bounds
        stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
        assertEquals(fullScreenBounds, task.getBounds());
        assertEquals(freeformBounds, task.mLastNonFullscreenBounds);

        // FREEFORM restores bounds
        stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
        assertEquals(freeformBounds, task.getBounds());
    }

    /**
     * This is a temporary hack to trigger an onConfigurationChange at the task level after an
     * orientation is requested. Normally this is done by the onDescendentOrientationChanged call
     * up the WM hierarchy, but since the WM hierarchy is mocked out, it doesn't happen here.
     * TODO: remove this when we either get a WM hierarchy or when hierarchies are merged.
     */
    private void setActivityRequestedOrientation(ActivityRecord activity, int orientation) {
        activity.setRequestedOrientation(orientation);
        ConfigurationContainer taskRecord = activity.getParent();
        taskRecord.onConfigurationChanged(taskRecord.getParent().getConfiguration());
    }

    /**
     * Tests that a task with forced orientation has orientation-consistent bounds within the
     * parent.
     */
    @Test
    public void testFullscreenBoundsForcedOrientation() {
        final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
        final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920);
        DisplayInfo info = new DisplayInfo();
        info.logicalWidth = fullScreenBounds.width();
        info.logicalHeight = fullScreenBounds.height();
        ActivityDisplay display = addNewActivityDisplayAt(info, POSITION_TOP);
        assertTrue(mRootActivityContainer.getActivityDisplay(display.mDisplayId) != null);
        ActivityStack stack = new StackBuilder(mRootActivityContainer)
                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
        TaskRecord task = stack.getChildAt(0);
        ActivityRecord root = task.getRootActivity();
        ActivityRecord top = new ActivityBuilder(mService).setTask(task).setStack(stack).build();
        assertEquals(root, task.getRootActivity());

        assertEquals(fullScreenBounds, task.getBounds());

        // Setting app to fixed portrait fits within parent
        setActivityRequestedOrientation(root, SCREEN_ORIENTATION_PORTRAIT);
        assertEquals(root, task.getRootActivity());
        assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation());
        assertTrue(task.getBounds().width() < task.getBounds().height());
        assertEquals(fullScreenBounds.height(), task.getBounds().height());

        // Setting non-root app has no effect
        setActivityRequestedOrientation(root, SCREEN_ORIENTATION_LANDSCAPE);
        assertTrue(task.getBounds().width() < task.getBounds().height());

        // Setting app to unspecified restores
        setActivityRequestedOrientation(root, SCREEN_ORIENTATION_UNSPECIFIED);
        assertEquals(fullScreenBounds, task.getBounds());

        // Setting app to fixed landscape and changing display
        setActivityRequestedOrientation(root, SCREEN_ORIENTATION_LANDSCAPE);
        display.setBounds(fullScreenBoundsPort);
        assertTrue(task.getBounds().width() > task.getBounds().height());
        assertEquals(fullScreenBoundsPort.width(), task.getBounds().width());

        // in FREEFORM, no constraint
        final Rect freeformBounds = new Rect(display.getBounds());
        freeformBounds.inset((int) (freeformBounds.width() * 0.2),
                (int) (freeformBounds.height() * 0.2));
        stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
        task.setBounds(freeformBounds);
        assertEquals(freeformBounds, task.getBounds());

        // FULLSCREEN letterboxes bounds
        stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
        assertTrue(task.getBounds().width() > task.getBounds().height());
        assertEquals(fullScreenBoundsPort.width(), task.getBounds().width());

        // FREEFORM restores bounds as before
        stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
        assertEquals(freeformBounds, task.getBounds());
    }

    /** Ensures that the alias intent won't have target component resolved. */