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

Commit a4269d0b authored by Vladimir Komsiyski's avatar Vladimir Komsiyski
Browse files

Intercept HOME intents for secondary home displays.

Compute (the likely) launch TaskDisplayArea early in ActivityStarter
before intercepting the intent and pass it to
ActiivtyStartInterceptor. If the launch display doesn't support
primary home but it supports secondary home, replace the intent with
the secondary home intent.

The existing RootWindowController#resolveSecondaryHomeActivity is
reused to consolidate the resolution logic into a single place. This
has the side effect that the ResolverActivity is skipped if there are
more than one CATEGORY_SECONDARY_HOME activities available but the
secondary home component that is started on new displays is the one
that is resolved, following the existing resolution logic. Note that
this is only the case if a CATEGORY_HOME intent is sent to the virtual
display. CATEGORY_SECONDARY_HOME intents are still resolved in the
old way. This is super nice as it complies with both the existing
intent handling logic and allows for a seamless home experience on VD

This also nicely allows for adding the display's custom home component
to the intent at a single place in the future.

Fix: 297168712
Test: atest ActivityStartInterceptorTest

Change-Id: I59e24f97b5df11372c17e50c9ea5751dbf973eb7
parent 9d287d85
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -2929,7 +2929,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        reparent(newTaskFrag, position);
    }

    private boolean isHomeIntent(Intent intent) {
    static boolean isHomeIntent(Intent intent) {
        return ACTION_MAIN.equals(intent.getAction())
                && (intent.hasCategory(CATEGORY_HOME)
                || intent.hasCategory(CATEGORY_SECONDARY_HOME))
+59 −6
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Pair;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;
@@ -77,7 +78,6 @@ class ActivityStartInterceptor {

    private final ActivityTaskManagerService mService;
    private final ActivityTaskSupervisor mSupervisor;
    private final RootWindowContainer mRootWindowContainer;
    private final Context mServiceContext;

    // UserManager cannot be final as it's not ready when this class is instantiated during boot
@@ -110,17 +110,23 @@ class ActivityStartInterceptor {
    TaskFragment mInTaskFragment;
    ActivityOptions mActivityOptions;

    /*
     * Note that this is just a hint of what the launch display area will be as it is
     * based only on the information at the early pre-interception stage of starting the
     * intent. The real launch display area calculated later may be different from this one.
     */
    TaskDisplayArea mPresumableLaunchDisplayArea;

    ActivityStartInterceptor(
            ActivityTaskManagerService service, ActivityTaskSupervisor supervisor) {
        this(service, supervisor, service.mRootWindowContainer, service.mContext);
        this(service, supervisor, service.mContext);
    }

    @VisibleForTesting
    ActivityStartInterceptor(ActivityTaskManagerService service, ActivityTaskSupervisor supervisor,
            RootWindowContainer root, Context context) {
            Context context) {
        mService = service;
        mSupervisor = supervisor;
        mRootWindowContainer = root;
        mServiceContext = context;
    }

@@ -162,7 +168,7 @@ class ActivityStartInterceptor {
    /**
     * A helper function to obtain the targeted {@link TaskFragment} during
     * {@link #intercept(Intent, ResolveInfo, ActivityInfo, String, Task, TaskFragment, int, int,
     * ActivityOptions)} if any.
     * ActivityOptions, TaskDisplayArea)} if any.
     */
    @Nullable
    private TaskFragment getLaunchTaskFragment() {
@@ -187,7 +193,7 @@ class ActivityStartInterceptor {
     */
    boolean intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType,
            Task inTask, TaskFragment inTaskFragment, int callingPid, int callingUid,
            ActivityOptions activityOptions) {
            ActivityOptions activityOptions, TaskDisplayArea presumableLaunchDisplayArea) {
        mUserManager = UserManager.get(mServiceContext);

        mIntent = intent;
@@ -199,6 +205,7 @@ class ActivityStartInterceptor {
        mInTask = inTask;
        mInTaskFragment = inTaskFragment;
        mActivityOptions = activityOptions;
        mPresumableLaunchDisplayArea = presumableLaunchDisplayArea;

        if (interceptQuietProfileIfNeeded()) {
            // If work profile is turned off, skip the work challenge since the profile can only
@@ -221,6 +228,11 @@ class ActivityStartInterceptor {
        if (interceptLockedManagedProfileIfNeeded()) {
            return true;
        }
        if (interceptHomeIfNeeded()) {
            // Replace primary home intents directed at displays that do not support primary home
            // but support secondary home with the relevant secondary home activity.
            return true;
        }

        final SparseArray<ActivityInterceptorCallback> callbacks =
                mService.getActivityInterceptorCallbacks();
@@ -470,6 +482,47 @@ class ActivityStartInterceptor {
        return true;
    }

    private boolean interceptHomeIfNeeded() {
        if (mPresumableLaunchDisplayArea == null || mService.mRootWindowContainer == null) {
            return false;
        }
        if (!ActivityRecord.isHomeIntent(mIntent)) {
            return false;
        }
        if (!mIntent.hasCategory(Intent.CATEGORY_HOME)) {
            // Already a secondary home intent, leave it alone.
            return false;
        }
        if (mService.mRootWindowContainer.shouldPlacePrimaryHomeOnDisplay(
                mPresumableLaunchDisplayArea.getDisplayId())) {
            // Primary home can be launched to the display area.
            return false;
        }
        if (!mService.mRootWindowContainer.shouldPlaceSecondaryHomeOnDisplayArea(
                mPresumableLaunchDisplayArea)) {
            // Secondary home cannot be launched on the display area.
            return false;
        }

        // At this point we have a primary home intent for a display that does not support primary
        // home activity but it supports secondary home one. So replace it with secondary home.
        Pair<ActivityInfo, Intent> info = mService.mRootWindowContainer
                .resolveSecondaryHomeActivity(mUserId, mPresumableLaunchDisplayArea);
        mIntent = info.second;
        // The new task flag is needed because the home activity should already be in the root task
        // and should not be moved to the caller's task. Also, activities cannot change their type,
        // e.g. a standard activity cannot become a home activity.
        mIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
        mCallingPid = mRealCallingPid;
        mCallingUid = mRealCallingUid;
        mResolvedType = null;

        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, /* flags= */ 0,
                mRealCallingUid, mRealCallingPid);
        mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, /*profilerInfo=*/ null);
        return true;
    }

    private boolean isPackageSuspended() {
        return mAInfo != null && mAInfo.applicationInfo != null
                && (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) != 0;
+12 −1
Original line number Diff line number Diff line
@@ -1154,10 +1154,12 @@ class ActivityStarter {
            }
        }

        final TaskDisplayArea suggestedLaunchDisplayArea =
                computeSuggestedLaunchDisplayArea(inTask, sourceRecord, checkedOptions);
        mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage,
                callingFeatureId);
        if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment,
                callingPid, callingUid, checkedOptions)) {
                callingPid, callingUid, checkedOptions, suggestedLaunchDisplayArea)) {
            // activity start was intercepted, e.g. because the target user is currently in quiet
            // mode (turn off work) or the target application is suspended
            intent = mInterceptor.mIntent;
@@ -1890,6 +1892,15 @@ class ActivityStarter {
        mPreferredWindowingMode = mLaunchParams.mWindowingMode;
    }

    private TaskDisplayArea computeSuggestedLaunchDisplayArea(
            Task task, ActivityRecord source, ActivityOptions options) {
        mSupervisor.getLaunchParamsController().calculate(task, /*layout=*/null,
                /*activity=*/ null, source, options, mRequest, PHASE_DISPLAY, mLaunchParams);
        return mLaunchParams.hasPreferredTaskDisplayArea()
                ? mLaunchParams.mPreferredTaskDisplayArea
                : mRootWindowContainer.getDefaultTaskDisplayArea();
    }

    @VisibleForTesting
    int isAllowedToStart(ActivityRecord r, boolean newTask, Task targetTask) {
        if (r.packageName == null) {
+14 −4
Original line number Diff line number Diff line
@@ -1607,6 +1607,19 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
                false /* allowInstrumenting */, false /* fromHomeKey */);
    }

    /**
     * Check if the display is valid for primary home activity.
     *
     * @param displayId The target display ID
     * @return {@code true} if allowed to launch, {@code false} otherwise.
     */
    boolean shouldPlacePrimaryHomeOnDisplay(int displayId) {
        // No restrictions to default display, vr 2d display or main display for visible users.
        return displayId == DEFAULT_DISPLAY || (displayId != INVALID_DISPLAY
                && (displayId == mService.mVr2dDisplayId
                || mWmService.shouldPlacePrimaryHomeOnDisplay(displayId)));
    }

    /**
     * Check if the display area is valid for secondary home activity.
     *
@@ -1680,10 +1693,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>

        final int displayId = taskDisplayArea != null ? taskDisplayArea.getDisplayId()
                : INVALID_DISPLAY;
        if (displayId == DEFAULT_DISPLAY || (displayId != INVALID_DISPLAY
                && (displayId == mService.mVr2dDisplayId
                || mWmService.shouldPlacePrimaryHomeOnDisplay(displayId)))) {
            // No restrictions to default display, vr 2d display or main display for visible users.
        if (shouldPlacePrimaryHomeOnDisplay(displayId)) {
            return true;
        }

+57 −30
Original line number Diff line number Diff line
@@ -118,12 +118,14 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
            root = activity;
        }

        if (root == null) {
        if (root == null && phase != PHASE_DISPLAY) {
            // There is a case that can lead us here. The caller is moving the top activity that is
            // in a task that has multiple activities to PIP mode. For that the caller is creating a
            // new task to host the activity so that we only move the top activity to PIP mode and
            // keep other activities in the previous task. There is no point to apply the launch
            // logic in this case.
            // However, for PHASE_DISPLAY the root may be null, but we still want to get a hint of
            // what the suggested launch display area would be.
            return RESULT_SKIP;
        }

@@ -395,8 +397,9 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
    }

    private TaskDisplayArea getPreferredLaunchTaskDisplayArea(@Nullable Task task,
            @Nullable ActivityOptions options, ActivityRecord source, LaunchParams currentParams,
            @NonNull ActivityRecord activityRecord, @Nullable Request request) {
            @Nullable ActivityOptions options, @Nullable ActivityRecord source,
            @Nullable LaunchParams currentParams, @Nullable ActivityRecord activityRecord,
            @Nullable Request request) {
        TaskDisplayArea taskDisplayArea = null;

        final WindowContainerToken optionLaunchTaskDisplayAreaToken = options != null
@@ -438,8 +441,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {

        // If the source activity is a no-display activity, pass on the launch display area token
        // from source activity as currently preferred.
        if (taskDisplayArea == null && source != null
                && source.noDisplay) {
        if (taskDisplayArea == null && source != null && source.noDisplay) {
            taskDisplayArea = source.mHandoverTaskDisplayArea;
            if (taskDisplayArea != null) {
                if (DEBUG) appendLog("display-area-from-no-display-source=" + taskDisplayArea);
@@ -478,21 +480,24 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
            }
        }

        if (taskDisplayArea == null) {
        if (taskDisplayArea == null && currentParams != null) {
            taskDisplayArea = currentParams.mPreferredTaskDisplayArea;
            if (DEBUG) appendLog("display-area-from-current-params=" + taskDisplayArea);
        }

        // Re-route to default display if the device didn't declare support for multi-display
        if (taskDisplayArea != null && !mSupervisor.mService.mSupportsMultiDisplay
                && taskDisplayArea.getDisplayId() != DEFAULT_DISPLAY) {
            taskDisplayArea = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
            if (DEBUG) appendLog("display-area-from-no-multidisplay=" + taskDisplayArea);
        }

        // Re-route to default display if the home activity doesn't support multi-display
        if (taskDisplayArea != null && activityRecord.isActivityTypeHome()
        if (taskDisplayArea != null && activityRecord != null && activityRecord.isActivityTypeHome()
                && !mSupervisor.mRootWindowContainer.canStartHomeOnDisplayArea(activityRecord.info,
                        taskDisplayArea, false /* allowInstrumenting */)) {
            taskDisplayArea = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
            if (DEBUG) appendLog("display-area-from-home=" + taskDisplayArea);
        }

        return (taskDisplayArea != null)
@@ -516,34 +521,56 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
     * @return {@link TaskDisplayArea} to house the task
     */
    private TaskDisplayArea getFallbackDisplayAreaForActivity(
            @NonNull ActivityRecord activityRecord, @Nullable Request request) {

        WindowProcessController controllerFromLaunchingRecord = mSupervisor.mService
                .getProcessController(activityRecord.launchedFromPid,
                        activityRecord.launchedFromUid);
        final TaskDisplayArea displayAreaForLaunchingRecord = controllerFromLaunchingRecord == null
                ? null : controllerFromLaunchingRecord.getTopActivityDisplayArea();
        if (displayAreaForLaunchingRecord != null) {
            return displayAreaForLaunchingRecord;
            @Nullable ActivityRecord activityRecord, @Nullable Request request) {
        if (activityRecord != null) {
            WindowProcessController controllerFromLaunchingRecord =
                    mSupervisor.mService.getProcessController(
                            activityRecord.launchedFromPid, activityRecord.launchedFromUid);
            if (controllerFromLaunchingRecord != null) {
                final TaskDisplayArea taskDisplayAreaForLaunchingRecord =
                        controllerFromLaunchingRecord.getTopActivityDisplayArea();
                if (taskDisplayAreaForLaunchingRecord != null) {
                    if (DEBUG) {
                        appendLog("display-area-for-launching-record="
                                + taskDisplayAreaForLaunchingRecord);
                    }
                    return taskDisplayAreaForLaunchingRecord;
                }
            }

        WindowProcessController controllerFromProcess = mSupervisor.mService.getProcessController(
            WindowProcessController controllerFromProcess =
                    mSupervisor.mService.getProcessController(
                            activityRecord.getProcessName(), activityRecord.getUid());
        final TaskDisplayArea displayAreaForRecord = controllerFromProcess == null ? null
                : controllerFromProcess.getTopActivityDisplayArea();
            if (controllerFromProcess != null) {
                final TaskDisplayArea displayAreaForRecord =
                        controllerFromProcess.getTopActivityDisplayArea();
                if (displayAreaForRecord != null) {
                    if (DEBUG) appendLog("display-area-for-record=" + displayAreaForRecord);
                    return displayAreaForRecord;
                }
            }
        }

        WindowProcessController controllerFromRequest = request == null ? null : mSupervisor
                .mService.getProcessController(request.realCallingPid, request.realCallingUid);
        final TaskDisplayArea displayAreaFromSourceProcess = controllerFromRequest == null ? null
                : controllerFromRequest.getTopActivityDisplayArea();
        if (request != null) {
            WindowProcessController controllerFromRequest =
                    mSupervisor.mService.getProcessController(
                            request.realCallingPid, request.realCallingUid);
            if (controllerFromRequest != null) {
                final TaskDisplayArea displayAreaFromSourceProcess =
                            controllerFromRequest.getTopActivityDisplayArea();
                if (displayAreaFromSourceProcess != null) {
                    if (DEBUG) {
                        appendLog("display-area-source-process=" + displayAreaFromSourceProcess);
                    }
                    return displayAreaFromSourceProcess;
                }
            }
        }

        return mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
        final TaskDisplayArea defaultTaskDisplayArea =
                mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
        if (DEBUG) appendLog("display-area-from-default-fallback=" + defaultTaskDisplayArea);
        return defaultTaskDisplayArea;
    }

    private boolean canInheritWindowingModeFromSource(@NonNull DisplayContent display,
Loading