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

Commit 6d0a6ddb authored by Jerry Chang's avatar Jerry Chang Committed by Automerger Merge Worker
Browse files

Merge "Consolidate the behavior and style of side stage outline" into sc-v2-dev am: 49c02984

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

Change-Id: I9a98cf4a2b13d153aeee45e30ab964a20249bb98
parents 775c10d3 49c02984
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<com.android.wm.shell.splitscreen.OutlineRoot
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
@@ -23,4 +23,4 @@
        android:layout_height="match_parent"
        android:layout_width="match_parent" />

</com.android.wm.shell.splitscreen.OutlineRoot>
</FrameLayout>
+93 −39
Original line number Diff line number Diff line
@@ -22,19 +22,23 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA
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.WindowInsets;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.WindowlessWindowManager;
import android.widget.FrameLayout;

import com.android.wm.shell.R;

@@ -45,17 +49,22 @@ import com.android.wm.shell.R;
class OutlineManager extends WindowlessWindowManager {
    private static final String WINDOW_NAME = "SplitOutlineLayer";
    private final Context mContext;
    private final Rect mOutlineBounds = new Rect();
    private final Rect mTmpBounds = new Rect();
    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;
    private int mOutlineColor;

    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
@@ -63,65 +72,110 @@ class OutlineManager extends WindowlessWindowManager {
        b.setParent(mHostLeash);
    }

    boolean drawOutlineBounds(Rect rootBounds) {
        if (mLeash == null || mViewHost == null) return false;

        computeOutlineBounds(mContext, rootBounds, mTmpBounds);
        if (mOutlineBounds.equals(mTmpBounds)) {
            return false;
        }
        mOutlineBounds.set(mTmpBounds);

        ((OutlineRoot) mViewHost.getView()).updateOutlineBounds(mOutlineBounds, mOutlineColor);
        final WindowManager.LayoutParams lp =
                (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
        lp.width = rootBounds.width();
        lp.height = rootBounds.height();
        mViewHost.relayout(lp);

        return true;
    }

    void inflate(SurfaceControl.Transaction t, SurfaceControl hostLeash, int color) {
    void inflate(SurfaceControl rootLeash, Rect rootBounds) {
        if (mLeash != null || mViewHost != null) return;

        mHostLeash = hostLeash;
        mOutlineColor = color;
        mHostLeash = rootLeash;
        mRootBounds.set(rootBounds);
        mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
        final OutlineRoot rootView = (OutlineRoot) LayoutInflater.from(mContext)

        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(rootView, lp);
        mViewHost.setView(rootLayout, lp);
        mLeash = getSurfaceControl(mViewHost.getWindowToken());
        t.setLayer(mLeash, Integer.MAX_VALUE);

        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));
        }

    private static void computeOutlineBounds(Context context, Rect rootBounds, Rect outBounds) {
        computeDisplayStableBounds(context, outBounds);
        outBounds.intersect(rootBounds);
        // Offset the coordinate from screen based to surface based.
        outBounds.offset(-rootBounds.left, -rootBounds.top);
    }

    private static void computeDisplayStableBounds(Context context, Rect outBounds) {
        final WindowMetrics windowMetrics =
                context.getSystemService(WindowManager.class).getMaximumWindowMetrics();
        outBounds.set(windowMetrics.getBounds());
        outBounds.inset(windowMetrics.getWindowInsets().getInsets(
                WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()));
    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);
    }
}
+0 −62
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.splitscreen;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.FrameLayout;

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

import com.android.wm.shell.R;

/** Root layout for holding split outline. */
public class OutlineRoot extends FrameLayout {
    public OutlineRoot(@NonNull Context context) {
        super(context);
    }

    public OutlineRoot(@NonNull Context context,
            @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public OutlineRoot(@NonNull Context context, @Nullable AttributeSet attrs,
            int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public OutlineRoot(@NonNull Context context, @Nullable AttributeSet attrs,
            int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    private OutlineView mOutlineView;

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mOutlineView = findViewById(R.id.split_outline);
    }

    void updateOutlineBounds(Rect bounds, int color) {
        mOutlineView.updateOutlineBounds(bounds, color);
    }
}
+33 −27
Original line number Diff line number Diff line
@@ -16,13 +16,17 @@

package com.android.wm.shell.splitscreen;

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.graphics.Rect;
import android.graphics.Region;
import android.util.AttributeSet;
import android.view.RoundedCorner;
import android.view.View;

import androidx.annotation.NonNull;
@@ -33,44 +37,46 @@ import com.android.internal.R;
/** View for drawing split outline. */
public class OutlineView extends View {
    private final Paint mPaint = new Paint();
    private final Rect mBounds = new Rect();

    public OutlineView(@NonNull Context context) {
        super(context);
    }
    private final Path mPath = new Path();
    private final float[] mRadii = new float[8];

    public OutlineView(@NonNull Context context,
            @Nullable AttributeSet attrs) {
    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));
    }

    public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    @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);
    }

    public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    private int getCornerRadius(@RoundedCorner.Position int position) {
        final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position);
        return roundedCorner == null ? 0 : roundedCorner.getRadius();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(getResources()
                .getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
    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);
        }

    void updateOutlineBounds(Rect bounds, int color) {
        if (mBounds.equals(bounds) && mPaint.getColor() == color) return;
        mBounds.set(bounds);
        mPaint.setColor(color);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mBounds.isEmpty()) return;
        final Path path = new Region(mBounds).getBoundaryPath();
        canvas.drawPath(path, mPaint);
        canvas.drawPath(mPath, mPaint);
    }

    @Override
    public boolean hasOverlappingRendering() {
        return false;
    }
}
+53 −20
Original line number Diff line number Diff line
@@ -17,15 +17,19 @@
package com.android.wm.shell.splitscreen;

import android.annotation.CallSuper;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;

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

/**
@@ -34,7 +38,8 @@ import com.android.wm.shell.common.SyncTransactionQueue;
 *
 * @see StageCoordinator
 */
class SideStage extends StageTaskListener {
class SideStage extends StageTaskListener implements
        DisplayInsetsController.OnInsetsChangedListener {
    private static final String TAG = SideStage.class.getSimpleName();
    private final Context mContext;
    private OutlineManager mOutlineManager;
@@ -77,33 +82,61 @@ class SideStage extends StageTaskListener {
        return true;
    }

    @Nullable
    public SurfaceControl getOutlineLeash() {
        return mOutlineManager.getOutlineLeash();
    }

    @Override
    @CallSuper
    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
        super.onTaskAppeared(taskInfo, leash);
        if (isRootTask(taskInfo)) {
            mOutlineManager = new OutlineManager(mContext, taskInfo.configuration);
            enableOutline(true);
        }
    }

    @Override
    @CallSuper
    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
        super.onTaskInfoChanged(taskInfo);
        if (isRootTask(taskInfo)) {
            mOutlineManager.setRootBounds(taskInfo.configuration.windowConfiguration.getBounds());
        }
    }

    private boolean isRootTask(ActivityManager.RunningTaskInfo taskInfo) {
        return mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId;
    }

    void enableOutline(boolean enable) {
        if (mOutlineManager == null) {
            return;
        }

        if (enable) {
            if (mOutlineManager == null && mRootTaskInfo != null) {
                mOutlineManager = new OutlineManager(mContext, mRootTaskInfo.configuration);
                mSyncQueue.runInSync(t -> mOutlineManager.inflate(t, mRootLeash, Color.YELLOW));
                updateOutlineBounds();
            if (mRootTaskInfo != null) {
                mOutlineManager.inflate(mRootLeash,
                        mRootTaskInfo.configuration.windowConfiguration.getBounds());
            }
        } else {
            if (mOutlineManager != null) {
            mOutlineManager.release();
                mOutlineManager = null;
            }
        }
    }

    private void updateOutlineBounds() {
        if (mOutlineManager == null || mRootTaskInfo == null || !mRootTaskInfo.isVisible) return;
        mOutlineManager.drawOutlineBounds(
                mRootTaskInfo.configuration.windowConfiguration.getBounds());
    void setOutlineVisibility(boolean visible) {
        mOutlineManager.setVisibility(visible);
    }

    @Override
    @CallSuper
    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
        super.onTaskInfoChanged(taskInfo);
        if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId) {
            updateOutlineBounds();
    public void insetsChanged(InsetsState insetsState) {
        mOutlineManager.onInsetsChanged(insetsState);
    }

    @Override
    public void insetsControlChanged(InsetsState insetsState,
            InsetsSourceControl[] activeControls) {
        insetsChanged(insetsState);
    }
}
Loading