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

Commit 43577653 authored by Tony Huang's avatar Tony Huang Committed by Automerger Merge Worker
Browse files

Merge "Copy current split codes to stagesplit package" into sc-v2-dev am: cbd22486 am: 3978711f

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16036448

Change-Id: I4e4161b8f6c319ca797743f319b457dfda0d4443
parents 1c8bc7b9 3978711f
Loading
Loading
Loading
Loading
+103 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.wm.shell.stagesplit;

import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.window.RemoteTransition;

import com.android.wm.shell.stagesplit.ISplitScreenListener;

/**
 * Interface that is exposed to remote callers to manipulate the splitscreen feature.
 */
interface ISplitScreen {

    /**
     * Registers a split screen listener.
     */
    oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1;

    /**
     * Unregisters a split screen listener.
     */
    oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;

    /**
     * Hides the side-stage if it is currently visible.
     */
    oneway void setSideStageVisibility(boolean visible) = 3;

    /**
     * Removes a task from the side stage.
     */
    oneway void removeFromSideStage(int taskId) = 4;

    /**
     * Removes the split-screen stages and leaving indicated task to top. Passing INVALID_TASK_ID
     * to indicate leaving no top task after leaving split-screen.
     */
    oneway void exitSplitScreen(int toTopTaskId) = 5;

    /**
     * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible.
     */
    oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6;

    /**
     * Starts a task in a stage.
     */
    oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7;

    /**
     * Starts a shortcut in a stage.
     */
    oneway void startShortcut(String packageName, String shortcutId, int stage, int position,
            in Bundle options, in UserHandle user) = 8;

    /**
     * Starts an activity in a stage.
     */
    oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage,
            int position, in Bundle options) = 9;

    /**
     * Starts tasks simultaneously in one transition.
     */
    oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
            in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10;

    /**
     * Version of startTasks using legacy transition system.
     */
     oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
                            int sideTaskId, in Bundle sideOptions, int sidePosition,
                            in RemoteAnimationAdapter adapter) = 11;

    /**
     * Blocking call that notifies and gets additional split-screen targets when entering
     * recents (for example: the dividerBar).
     * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled).
     * @param appTargets apps that will be re-parented to display area
     */
    RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
                                                   in RemoteAnimationTarget[] appTargets) = 12;
}
+33 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.wm.shell.stagesplit;

/**
 * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
 */
oneway interface ISplitScreenListener {

    /**
     * Called when the stage position changes.
     */
    void onStagePositionChanged(int stage, int position);

    /**
     * Called when a task changes stages.
     */
    void onTaskStageChanged(int taskId, int stage, boolean visible);
}
 No newline at end of file
+104 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.wm.shell.stagesplit;

import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;

import android.annotation.Nullable;
import android.graphics.Rect;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;

import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;

/**
 * Main stage for split-screen mode. When split-screen is active all standard activity types launch
 * on the main stage, except for task that are explicitly pinned to the {@link SideStage}.
 * @see StageCoordinator
 */
class MainStage extends StageTaskListener {
    private static final String TAG = MainStage.class.getSimpleName();

    private boolean mIsActive = false;

    MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
            StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
            SurfaceSession surfaceSession,
            @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
        super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
                stageTaskUnfoldController);
    }

    boolean isActive() {
        return mIsActive;
    }

    void activate(Rect rootBounds, WindowContainerTransaction wct) {
        if (mIsActive) return;

        final WindowContainerToken rootToken = mRootTaskInfo.token;
        wct.setBounds(rootToken, rootBounds)
                .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW)
                .setLaunchRoot(
                        rootToken,
                        CONTROLLED_WINDOWING_MODES,
                        CONTROLLED_ACTIVITY_TYPES)
                .reparentTasks(
                        null /* currentParent */,
                        rootToken,
                        CONTROLLED_WINDOWING_MODES,
                        CONTROLLED_ACTIVITY_TYPES,
                        true /* onTop */)
                // Moving the root task to top after the child tasks were re-parented , or the root
                // task cannot be visible and focused.
                .reorder(rootToken, true /* onTop */);

        mIsActive = true;
    }

    void deactivate(WindowContainerTransaction wct) {
        deactivate(wct, false /* toTop */);
    }

    void deactivate(WindowContainerTransaction wct, boolean toTop) {
        if (!mIsActive) return;
        mIsActive = false;

        if (mRootTaskInfo == null) return;
        final WindowContainerToken rootToken = mRootTaskInfo.token;
        wct.setLaunchRoot(
                        rootToken,
                        null,
                        null)
                .reparentTasks(
                        rootToken,
                        null /* newParent */,
                        CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
                        CONTROLLED_ACTIVITY_TYPES,
                        toTop)
                // We want this re-order to the bottom regardless since we are re-parenting
                // all its tasks.
                .reorder(rootToken, false /* onTop */);
    }

    void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) {
        wct.setBounds(mRootTaskInfo.token, bounds)
                .setWindowingMode(mRootTaskInfo.token, windowingMode);
    }
}
+181 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.wm.shell.stagesplit;

import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Binder;
import android.view.IWindow;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.widget.FrameLayout;

import com.android.wm.shell.R;

/**
 * Handles drawing outline of the bounds of provided root surface. The outline will be drown with
 * the consideration of display insets like status bar, navigation bar and display cutout.
 */
class OutlineManager extends WindowlessWindowManager {
    private static final String WINDOW_NAME = "SplitOutlineLayer";
    private final Context mContext;
    private final Rect mRootBounds = new Rect();
    private final Rect mTempRect = new Rect();
    private final Rect mLastOutlineBounds = new Rect();
    private final InsetsState mInsetsState = new InsetsState();
    private final int mExpandedTaskBarHeight;
    private OutlineView mOutlineView;
    private SurfaceControlViewHost mViewHost;
    private SurfaceControl mHostLeash;
    private SurfaceControl mLeash;

    OutlineManager(Context context, Configuration configuration) {
        super(configuration, null /* rootSurface */, null /* hostInputToken */);
        mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
                null /* options */);
        mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.taskbar_frame_height);
    }

    @Override
    protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
        b.setParent(mHostLeash);
    }

    void inflate(SurfaceControl rootLeash, Rect rootBounds) {
        if (mLeash != null || mViewHost != null) return;

        mHostLeash = rootLeash;
        mRootBounds.set(rootBounds);
        mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);

        final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext)
                .inflate(R.layout.split_outline, null);
        mOutlineView = rootLayout.findViewById(R.id.split_outline);

        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
        lp.width = mRootBounds.width();
        lp.height = mRootBounds.height();
        lp.token = new Binder();
        lp.setTitle(WINDOW_NAME);
        lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
        // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
        //  TRUSTED_OVERLAY for windowless window without input channel.
        mViewHost.setView(rootLayout, lp);
        mLeash = getSurfaceControl(mViewHost.getWindowToken());

        drawOutline();
    }

    void release() {
        if (mViewHost != null) {
            mViewHost.release();
            mViewHost = null;
        }
        mRootBounds.setEmpty();
        mLastOutlineBounds.setEmpty();
        mOutlineView = null;
        mHostLeash = null;
        mLeash = null;
    }

    @Nullable
    SurfaceControl getOutlineLeash() {
        return mLeash;
    }

    void setVisibility(boolean visible) {
        if (mOutlineView != null) {
            mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
        }
    }

    void setRootBounds(Rect rootBounds) {
        if (mViewHost == null || mViewHost.getView() == null) {
            return;
        }

        if (!mRootBounds.equals(rootBounds)) {
            WindowManager.LayoutParams lp =
                    (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
            lp.width = rootBounds.width();
            lp.height = rootBounds.height();
            mViewHost.relayout(lp);
            mRootBounds.set(rootBounds);
            drawOutline();
        }
    }

    void onInsetsChanged(InsetsState insetsState) {
        if (!mInsetsState.equals(insetsState)) {
            mInsetsState.set(insetsState);
            drawOutline();
        }
    }

    private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) {
        outBounds.set(rootBounds);
        final InsetsSource taskBarInsetsSource =
                insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
        // Only insets the divider bar with task bar when it's expanded so that the rounded corners
        // will be drawn against task bar.
        if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
            outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds));
        }

        // Offset the coordinate from screen based to surface based.
        outBounds.offset(-rootBounds.left, -rootBounds.top);
    }

    void drawOutline() {
        if (mOutlineView == null) {
            return;
        }

        computeOutlineBounds(mRootBounds, mInsetsState, mTempRect);
        if (mTempRect.equals(mLastOutlineBounds)) {
            return;
        }

        ViewGroup.MarginLayoutParams lp =
                (ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams();
        lp.leftMargin = mTempRect.left;
        lp.topMargin = mTempRect.top;
        lp.width = mTempRect.width();
        lp.height = mTempRect.height();
        mOutlineView.setLayoutParams(lp);
        mLastOutlineBounds.set(mTempRect);
    }
}
+82 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.wm.shell.stagesplit;

import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
import static android.view.RoundedCorner.POSITION_TOP_LEFT;
import static android.view.RoundedCorner.POSITION_TOP_RIGHT;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.RoundedCorner;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.R;

/** View for drawing split outline. */
public class OutlineView extends View {
    private final Paint mPaint = new Paint();
    private final Path mPath = new Path();
    private final float[] mRadii = new float[8];

    public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(
                getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
        mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null));
    }

    @Override
    protected void onAttachedToWindow() {
        // TODO(b/200850654): match the screen corners with the actual display decor.
        mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT);
        mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT);
        mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT);
        mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT);
    }

    private int getCornerRadius(@RoundedCorner.Position int position) {
        final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position);
        return roundedCorner == null ? 0 : roundedCorner.getRadius();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (changed) {
            mPath.reset();
            mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawPath(mPath, mPaint);
    }

    @Override
    public boolean hasOverlappingRendering() {
        return false;
    }
}
Loading