PIP模式详述

一、进入PIP模式

1、概述流程

创建pip stack → 创建新的TaskRecord → activity reparent到newTask → newTask reparent到pip stack → resumeFocusedStacksTopActivitive

2、时序图
PIP模式详述_第1张图片
3、相关代码解析
app端binder回调到ATMS的方法,如果有keyguard,先dismiss keyguard再进入pip

// ActivityTaskManagerService
@Override
public boolean enterPictureInPictureMode(IBinder token, final PictureInPictureParams params) {
    final long origId = Binder.clearCallingIdentity();
    try {
        synchronized (mGlobalLock) {
            final ActivityRecord r = ensureValidPictureInPictureActivityParamsLocked(
                    "enterPictureInPictureMode", token, params);
 
            // If the activity is already in picture in picture mode, then just return early
            if (isInPictureInPictureMode(r)) {
                return true;
            }
 
            // Activity supports picture-in-picture, now check that we can enter PiP at this
            // point, if it is
            // 是否能进入pip的一些判断处理,activity是否配置,keyguard、activity state等
            if (!r.checkEnterPictureInPictureState("enterPictureInPictureMode",
                    false /* beforeStopping */)) {
                return false;
            }
 
            final Runnable enterPipRunnable = () -> {
                synchronized (mGlobalLock) {
                    if (r.finishing || r.getTaskRecord() == null) return;
                    // Only update the saved args from the args that are set
                    r.pictureInPictureArgs.copyOnlySet(params);
                    final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
                    final List actions = r.pictureInPictureArgs.getActions();
                    // Adjust the source bounds by the insets for the transition down
                    final Rect sourceBounds = new Rect(
                            r.pictureInPictureArgs.getSourceRectHint());
                    mRootActivityContainer.moveActivityToPinnedStack(
                            r, sourceBounds, aspectRatio, "enterPictureInPictureMode");
                    final ActivityStack stack = r.getActivityStack();
                    stack.setPictureInPictureAspectRatio(aspectRatio);
                    stack.setPictureInPictureActions(actions);
                    MetricsLoggerWrapper.logPictureInPictureEnter(mContext, r.appInfo.uid,
                            r.shortComponentName, r.supportsEnterPipOnTaskSwitch);
                    logPictureInPictureArgs(params);
                }
            };
            // keyguard显示,先dismiss keyguard再进入pip,否则直接进入pip
 
            if (isKeyguardLocked()) {
                // If the keyguard is showing or occluded, then try and dismiss it before
                // entering picture-in-picture (this will prompt the user to authenticate if the
                // device is currently locked).
                dismissKeyguard(token, new KeyguardDismissCallback() {
                    @Override
                    public void onDismissSucceeded() {
                        mH.post(enterPipRunnable);
                    }
                }, null /* message */);
            } else {
                // Enter picture in picture immediately otherwise
                enterPipRunnable.run();
            }
            return true;
        }
    } finally {
        Binder.restoreCallingIdentity(origId);
    }
}
// task stack 动画等相关处理
// RootActivityContainer
void moveActivityToPinnedStack(ActivityRecord r, Rect sourceHintBounds, float aspectRatio,
        String reason) {
 
    mWindowManager.deferSurfaceLayout();
 
    final ActivityDisplay display = r.getActivityStack().getDisplay();
    // 一般情况下获取都为null,创建pip stack时会赋值,见如下:2-1)
    ActivityStack stack = display.getPinnedStack();
 
    // This will clear the pinned stack by moving an existing task to the full screen stack,
    // ensuring only one task is present.
    if (stack != null) {
        mStackSupervisor.moveTasksToFullscreenStackLocked(stack, !ON_TOP);
    }
 
    // Need to make sure the pinned stack exist so we can resize it below...
    stack = display.getOrCreateStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP);
 
    // Calculate the target bounds here before the task is reparented back into pinned windowing
    // mode (which will reset the saved bounds)
    // Rect(508, 1869 - 1036, 2166)
    // 计算pip stack边界
    final Rect destBounds = stack.getDefaultPictureInPictureBounds(aspectRatio);
 
    try {
        final TaskRecord task = r.getTaskRecord();
        // Resize the pinned stack to match the current size of the task the activity we are
        // going to be moving is currently contained in. We do this to have the right starting
        // animation bounds for the pinned stack to the desired bounds the caller wants.
        // 没看到有太大作用
        resizeStack(stack, task.getRequestedOverrideBounds(), null /* tempTaskBounds */,
                null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
                true /* allowResizeInDockedMode */, !DEFER_RESUME);
 
        // 一般task的mActivities大小会大于1,因为会有主页,再是视频详情页
        if (task.mActivities.size() == 1) {
            // Defer resume until below, and do not schedule PiP changes until we animate below
            task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, DEFER_RESUME,
                    false /* schedulePictureInPictureModeChange */, reason);
        } else {
            // There are multiple activities in the task and moving the top activity should
            // reveal/leave the other activities in their original task.
 
            // Currently, we don't support reparenting activities across tasks in two different
            // stacks, so instead, just create a new task in the same stack, reparent the
            // activity into that task, and then reparent the whole task to the new stack. This
            // ensures that all the necessary work to migrate states in the old and new stacks
            // is also done.
            // 在原有stack上新建一个task
            final TaskRecord newTask = task.getStack().createTaskRecord(
                    mStackSupervisor.getNextTaskIdForUserLocked(r.mUserId), r.info,
                    r.intent, null, null, true);
            // Activity reparent到新建的task
            r.reparent(newTask, MAX_VALUE, "moveActivityToStack");
 
            // Defer resume until below, and do not schedule PiP changes until we animate below
            // task reparent到pip stack
            newTask.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
                    DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason);
        }
 
        // Reset the state that indicates it can enter PiP while pausing after we've moved it
        // to the pinned stack
        r.supportsEnterPipOnTaskSwitch = false;
    } finally {
        mWindowManager.continueSurfaceLayout();
    }
 
    stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */,
            true /* fromFullscreen */);
 
    // Update the visibility of all activities after the they have been reparented to the new
    // stack.  This MUST run after the animation above is scheduled to ensure that the windows
    // drawn signal is scheduled after the bounds animation start call on the bounds animator
    // thread.
    // 显示
    ensureActivitiesVisible(null, 0, false /* preserveWindows */);
    resumeFocusedStacksTopActivities();
 
    mService.getTaskChangeNotificationController().notifyActivityPinned(r);
}
// 根据fromFullscreen判断进入还是退出pip,执行相关动画
// TaskStack
    /** Do not schedule any PiP mode changed callbacks as a part of this animation. */
    public static final int NO_PIP_MODE_CHANGED_CALLBACKS = 0;
    /** Schedule a PiP mode changed callback when this animation starts. */
    public static final int SCHEDULE_PIP_MODE_CHANGED_ON_START = 1;
    /** Schedule a PiP mode changed callback when this animation ends. */
    public static final int SCHEDULE_PIP_MODE_CHANGED_ON_END = 2;
 
void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds,
        int animationDuration, boolean fromFullscreen) {
    if (!inPinnedWindowingMode()) {
        return;
    }
 
    // Get non-null fullscreen to-bounds for animating if the bounds are null
    @SchedulePipModeChangedState int schedulePipModeChangedState =
            NO_PIP_MODE_CHANGED_CALLBACKS;
    final boolean toFullscreen = toBounds == null;
    // 退出pip
    if (toFullscreen) {
        if (fromFullscreen) {
            throw new IllegalArgumentException("Should not defer scheduling PiP mode"
                    + " change on animation to fullscreen.");
        }
        schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_START;
 
        mWmService.getStackBounds(
                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mTmpToBounds);
        if (!mTmpToBounds.isEmpty()) {
            // If there is a fullscreen bounds, use that
            toBounds = new Rect(mTmpToBounds);
        } else {
            // Otherwise, use the display bounds
            toBounds = new Rect();
            getDisplayContent().getBounds(toBounds);
        }
     // 进入pip
    } else if (fromFullscreen) {
        schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
    }
    // 设置动画的边界Rect(0, 0 - 1080, 2340) 到 Rect(508, 1869 - 1036, 2166)
    setAnimationFinalBounds(sourceHintBounds, toBounds, toFullscreen);
 
    final Rect finalToBounds = toBounds;
    final @SchedulePipModeChangedState int finalSchedulePipModeChangedState =
            schedulePipModeChangedState;
    final DisplayContent displayContent = getDisplayContent();
    @BoundsAnimationController.AnimationType int intendedAnimationType =
            displayContent.mBoundsAnimationController.getAnimationType();
    if (intendedAnimationType == FADE_IN) {
        if (fromFullscreen) {
            setPinnedStackAlpha(0f);
        }
        if (toBounds.width() == fromBounds.width()
                && toBounds.height() == fromBounds.height()) {
            intendedAnimationType = BoundsAnimationController.BOUNDS;
        }
    }
 
    final @BoundsAnimationController.AnimationType int animationType = intendedAnimationType;
    mCancelCurrentBoundsAnimation = false;
    displayContent.mBoundsAnimationController.getHandler().post(() -> {
        displayContent.mBoundsAnimationController.animateBounds(this, fromBounds,
                finalToBounds, animationDuration, finalSchedulePipModeChangedState,
                fromFullscreen, toFullscreen, animationType);
    });
}

二、退出PIP模式

1、概述流程

执行退出动画 → 把对应的task 移到全屏的stack → resume stack的activiy
相关log

02-18 16:56:22.212 1295 1315 I am_remove_task: [27,13]
02-18 16:56:22.219 1295 1315 I am_focused_stack: [0,0,14,0,moveTasksToFullscreenStack - onTop]

2、时序图
PIP模式详述_第2张图片
3、相关代码解析

// ActivityStackSupervisor
/**
 * TODO: This should just change the windowing mode and resize vs. actually moving task around.
 * Can do that once we are no longer using static stack ids.
 */
private void moveTasksToFullscreenStackInSurfaceTransaction(ActivityStack fromStack,
        int toDisplayId, boolean onTop) {
 
    mWindowManager.deferSurfaceLayout();
    try {
        final int windowingMode = fromStack.getWindowingMode();
        final boolean inPinnedWindowingMode = windowingMode == WINDOWING_MODE_PINNED;
        final ActivityDisplay toDisplay =
                mRootActivityContainer.getActivityDisplay(toDisplayId);
        ......
        // If we are moving from the pinned stack, then the animation takes care of updating
        // the picture-in-picture mode.
        final boolean schedulePictureInPictureModeChange = inPinnedWindowingMode;
        final ArrayList tasks = fromStack.getAllTasks();
 
        if (!tasks.isEmpty()) {
            mTmpOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
            final int size = tasks.size();
            for (int i = 0; i < size; ++i) {
                final TaskRecord task = tasks.get(i);
                final ActivityStack toStack = toDisplay.getOrCreateStack(
                            null, mTmpOptions, task, task.getActivityType(), onTop);
 
                if (onTop) {
                    final boolean isTopTask = i == (size - 1);
                    // Defer resume until all the tasks have been moved to the fullscreen stack
                    task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
                            isTopTask /* animate */, DEFER_RESUME,
                            schedulePictureInPictureModeChange,
                            "moveTasksToFullscreenStack - onTop");
                    MetricsLoggerWrapper.logPictureInPictureFullScreen(mService.mContext,
                            task.effectiveUid, task.realActivity.flattenToString());
                } else {
                    // Position the tasks in the fullscreen stack in order at the bottom of the
                    // stack. Also defer resume until all the tasks have been moved to the
                    // fullscreen stack.
                    task.reparent(toStack, ON_TOP,
                            REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME,
                            schedulePictureInPictureModeChange,
                            "moveTasksToFullscreenStack - NOT_onTop");
                }
            }
        }
 
        mRootActivityContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
        mRootActivityContainer.resumeFocusedStacksTopActivities();
    } finally {
        mAllowDockedStackResize = true;
        mWindowManager.continueSurfaceLayout();
    }
}

你可能感兴趣的:(AMS,android)