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

Commit b5cc09fe authored by Garfield Tan's avatar Garfield Tan
Browse files

Implement launch bounds logic in Android (2/3)

This CL implements the biggest chunk of launch bounds logic in Android
branch and combine ActivityLaunchParamsModifier logic into
TaskLaunchParamsModifier. It left some things to be implemented:
1) It didn't yet consider persisting/recovering data;
2) It didn't implement letterboxing/pillarboxing, but according to
offline chat this should be enforced after launch bounds policies by
system;
3) Immersive mode is not yet implemented, but that's more tied to
recovering previous immersive mode and we won't launch apps to immersive
mode directly in any case;
4) No last seen non-fullscreen bounds are set if display is fullscreen,
which could be useful when display windowing mode changes from
fullscreen to freeform at later time.

There are also some topics that for sure need future discussions, so I
left them out of this CL as well:
1) App controlled apps (not only bounds specified in ActivityOptions);
2) Metadata indicating that the app prefers tablet-like bounds for
freeform windows (i.e. w/o limiting window size to Nexus 5x screen
size);
3) Fixed maximized size, which indicates that the maximized size
shouldn't be changed due to display resolution or orientation changes;
4) What to do if app requests to launch an activity without any flag
that indicates a new task should be used, but with a preferred display
ID/bounds set to a different value than its current window;
5) Should insets be considered in launch bounds (AM side) or on WM side,
IIUC freeform windows don't need to consider insets, and fullscreen
windows are covered by WM.

Our policy also has special treatment for Chrome snapped windows
(side-by-side mode in our tablet), which may not make much sense in
Android.

Bug: 113252871
Test: go/wm-smoke. Try launching some freeform windows on secondary
displays.
atest FrameworksServicesTests:TaskLaunchParamsModifierTests
atest ActivityManagerManifestLayoutTests
Change-Id: I974031725015b6283f33b9076788e7ce45134690
parent aeeb3f5e
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -1408,5 +1408,13 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
         * @attr ref android.R.styleable#AndroidManifestLayout_minHeight
         */
        public final int minHeight;

        /**
         * Returns if this {@link WindowLayout} has specified bounds.
         * @hide
         */
        public boolean hasSpecifiedSize() {
            return width >= 0 || height >= 0 || widthFraction >= 0 || heightFraction >= 0;
        }
    }
}
+0 −65
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.server.am;

import android.app.ActivityOptions;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;

import com.android.server.am.LaunchParamsController.LaunchParams;
import com.android.server.am.LaunchParamsController.LaunchParamsModifier;

/**
 * An implementation of {@link LaunchParamsModifier}, which applies the launch bounds specified
 * inside {@link ActivityOptions#getLaunchBounds()}.
 */
public class ActivityLaunchParamsModifier implements LaunchParamsModifier {
    private final ActivityStackSupervisor mSupervisor;

    ActivityLaunchParamsModifier(ActivityStackSupervisor activityStackSupervisor) {
        mSupervisor = activityStackSupervisor;
    }

    @Override
    public int onCalculate(TaskRecord task, ActivityInfo.WindowLayout layout,
            ActivityRecord activity, ActivityRecord source, ActivityOptions options,
            LaunchParams currentParams, LaunchParams outParams) {
        // We only care about figuring out bounds for activities.
        if (activity == null) {
            return RESULT_SKIP;
        }

        // Activity must be resizeable in the specified task.
        if (!(mSupervisor.canUseActivityOptionsLaunchBounds(options)
                && (activity.isResizeable() || (task != null && task.isResizeable())))) {
            return RESULT_SKIP;
        }

        final Rect bounds = options.getLaunchBounds();

        // Bounds weren't valid.
        if (bounds == null || bounds.isEmpty()) {
            return RESULT_SKIP;
        }

        outParams.mBounds.set(bounds);

        // When this is the most explicit position specification so we should not allow further
        // modification of the position.
        return RESULT_DONE;
    }
}
+1 −2
Original line number Diff line number Diff line
@@ -163,7 +163,6 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -228,7 +227,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
    }

    @Override
    protected ConfigurationContainer getChildAt(int index) {
    protected TaskRecord getChildAt(int index) {
        return mTaskHistory.get(index);
    }

+25 −40
Original line number Diff line number Diff line
@@ -1606,13 +1606,18 @@ class ActivityStarter {
        mVoiceSession = voiceSession;
        mVoiceInteractor = voiceInteractor;

        mPreferredDisplayId = getPreferedDisplayId(mSourceRecord, mStartActivity, options);

        mLaunchParams.reset();

        mSupervisor.getLaunchParamsController().calculate(inTask, null /*layout*/, r, sourceRecord,
                options, mLaunchParams);

        if (mLaunchParams.hasPreferredDisplay()) {
            mPreferredDisplayId = mLaunchParams.mPreferredDisplayId;
        } else {
            mPreferredDisplayId = DEFAULT_DISPLAY;
        }
        ensureValidPreferredDisplayId(r);

        mLaunchMode = r.launchMode;

        mLaunchFlags = adjustLaunchFlagsToDocumentMode(
@@ -1704,6 +1709,24 @@ class ActivityStarter {
        mNoAnimation = (mLaunchFlags & FLAG_ACTIVITY_NO_ANIMATION) != 0;
    }

    /**
     * Ensure preferred display ID matches the starting activity.
     */
    private void ensureValidPreferredDisplayId(ActivityRecord startingActivity) {
        // Check if the Activity is a VR activity. If so, the activity should be launched in
        // main display.
        if (startingActivity != null && startingActivity.requestedVrComponent != null) {
            mPreferredDisplayId = DEFAULT_DISPLAY;
        }

        // Get the virtual display ID from ActivityStackManagerService. If that's set we should
        // always use that.
        final int displayId = mService.mVr2dDisplayId;
        if (displayId != INVALID_DISPLAY) {
            mPreferredDisplayId = displayId;
        }
    }

    private void sendNewTaskResultRequestIfNeeded() {
        final ActivityStack sourceStack = mStartActivity.resultTo != null
                ? mStartActivity.resultTo.getStack() : null;
@@ -1882,44 +1905,6 @@ class ActivityStarter {
        return intentActivity;
    }

    /**
     * Returns the ID of the display to use for a new activity. If the device is in VR mode,
     * then return the Vr mode's virtual display ID. If not,  if the activity was started with
     * a launchDisplayId, use that. Otherwise, if the source activity has a explicit display ID
     * set, use that to launch the activity.
     */
    private int getPreferedDisplayId(
            ActivityRecord sourceRecord, ActivityRecord startingActivity, ActivityOptions options) {
        // Check if the Activity is a VR activity. If so, the activity should be launched in
        // main display.
        if (startingActivity != null && startingActivity.requestedVrComponent != null) {
            return DEFAULT_DISPLAY;
        }

        // Get the virtual display id from ActivityManagerService.
        int displayId = mService.mVr2dDisplayId;
        if (displayId != INVALID_DISPLAY) {
            if (DEBUG_STACK) {
                Slog.d(TAG, "getSourceDisplayId :" + displayId);
            }
            return displayId;
        }

        // If the caller requested a display, prefer that display.
        final int launchDisplayId =
                (options != null) ? options.getLaunchDisplayId() : INVALID_DISPLAY;
        if (launchDisplayId != INVALID_DISPLAY) {
            return launchDisplayId;
        }

        displayId = sourceRecord != null ? sourceRecord.getDisplayId() : INVALID_DISPLAY;
        // If the activity has a displayId set explicitly, launch it on the same displayId.
        if (displayId != INVALID_DISPLAY) {
            return displayId;
        }
        return DEFAULT_DISPLAY;
    }

    /**
     * Figure out which task and activity to bring to front when we have found an existing matching
     * activity record in history. May also clear the task if needed.
+38 −28
Original line number Diff line number Diff line
@@ -16,6 +16,13 @@

package com.android.server.am;

import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;

import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;

import android.annotation.IntDef;
import android.app.ActivityOptions;
import android.content.pm.ActivityInfo.WindowLayout;
@@ -26,13 +33,6 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;

import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;

import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;

/**
 * {@link LaunchParamsController} calculates the {@link LaunchParams} by coordinating between
 * registered {@link LaunchParamsModifier}s.
@@ -58,11 +58,7 @@ class LaunchParamsController {
     */
    void registerDefaultModifiers(ActivityStackSupervisor supervisor) {
        // {@link TaskLaunchParamsModifier} handles window layout preferences.
        registerModifier(new TaskLaunchParamsModifier());

        // {@link ActivityLaunchParamsModifier} is the most specific modifier and thus should be
        // registered last (applied first) out of the defaults.
        registerModifier(new ActivityLaunchParamsModifier(supervisor));
        registerModifier(new TaskLaunchParamsModifier(supervisor));
    }

    /**
@@ -226,27 +222,41 @@ class LaunchParamsController {
        @IntDef({RESULT_SKIP, RESULT_DONE, RESULT_CONTINUE})
        @interface Result {}

        // Returned when the modifier does not want to influence the bounds calculation
        /** Returned when the modifier does not want to influence the bounds calculation */
        int RESULT_SKIP = 0;
        // Returned when the modifier has changed the bounds and would like its results to be the
        // final bounds applied.
        /**
         * Returned when the modifier has changed the bounds and would like its results to be the
         * final bounds applied.
         */
        int RESULT_DONE = 1;
        // Returned when the modifier has changed the bounds but is okay with other modifiers
        // influencing the bounds.
        /**
         * Returned when the modifier has changed the bounds but is okay with other modifiers
         * influencing the bounds.
         */
        int RESULT_CONTINUE = 2;

        /**
         * Called when asked to calculate {@link LaunchParams}.
         * @param task            The {@link TaskRecord} currently being positioned.
         * @param layout          The specified {@link WindowLayout}.
         * @param activity        The {@link ActivityRecord} currently being positioned.
         * @param source          The {@link ActivityRecord} activity was started from.
         * @param options         The {@link ActivityOptions} specified for the activity.
         * @param currentParams   The current {@link LaunchParams}. This can differ from the initial
         *                        params as it represents the modified params up to this point.
         * @param outParams       The resulting {@link LaunchParams} after all calculations.
         * @return                A {@link Result} representing the result of the
         *                        {@link LaunchParams} calculation.
         * Returns the launch params that the provided activity launch params should be overridden
         * to. {@link LaunchParamsModifier} can use this for various purposes, including: 1)
         * Providing default bounds if the launch bounds have not been provided. 2) Repositioning
         * the task so it doesn't get placed over an existing task. 3) Resizing the task so that its
         * dimensions match the activity's requested orientation.
         *
         * @param task          Can be: 1) the target task in which the source activity wants to
         *                      launch the target activity; 2) a newly created task that Android
         *                      gives a chance to override its launching bounds; 3) {@code null} if
         *                      this is called to override an activity's launching bounds.
         * @param layout        Desired layout when activity is first launched.
         * @param activity      Activity that is being started. This can be {@code null} on
         *                      re-parenting an activity to a new task (e.g. for
         *                      Picture-In-Picture). Tasks being created because an activity was
         *                      launched should have this be non-null.
         * @param source        the Activity that launched a new task. Could be {@code null}.
         * @param options       {@link ActivityOptions} used to start the activity with.
         * @param currentParams launching params after the process of last {@link
         *                      LaunchParamsModifier}.
         * @param outParams     the result params to be set.
         * @return see {@link LaunchParamsModifier.Result}
         */
        @Result
        int onCalculate(TaskRecord task, WindowLayout layout, ActivityRecord activity,
Loading