Add Tv Pip rounded corners tray

This CL adds a permanent grey border around the pip surface, which
has rounded corners, and a backing surface, with the same color as the
border. This way the pip surface is sandwiched between the two pip menu
surfaces

Bug: 219883374
Test: m && open app in expanded pip && move pip to opposite edge
    && start dream && wake up dream && toggle expanded pip
Change-Id: Idf22ca22044e7e5e3ca6b39326e3afb76e3ee43c
This commit is contained in:
Galia Peycheva
2022-03-01 08:19:35 +01:00
parent 7436573ad9
commit d2d5e68312
11 changed files with 320 additions and 118 deletions

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/pip_menu_background_corner_radius" />
<solid android:color="@color/tv_pip_menu_background"/>
<stroke android:width="@dimen/pip_menu_border_width"
android:color="@color/tv_pip_menu_background"/>
</shape>

View File

@@ -14,9 +14,20 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/pip_menu_border_radius" />
<stroke android:width="@dimen/pip_menu_border_width"
android:color="@color/tv_pip_menu_focus_border" />
</shape>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="@integer/pip_menu_fade_animation_duration">
<item android:state_activated="true">
<shape android:shape="rectangle">
<corners android:radius="@dimen/pip_menu_border_corner_radius" />
<stroke android:width="@dimen/pip_menu_border_width"
android:color="@color/tv_pip_menu_focus_border" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<corners android:radius="@dimen/pip_menu_border_corner_radius" />
<stroke android:width="@dimen/pip_menu_border_width"
android:color="@color/tv_pip_menu_background"/>
</shape>
</item>
</selector>

View File

@@ -16,9 +16,10 @@
-->
<!-- Layout for TvPipMenuView -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_pip_menu"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:id="@+id/tv_pip_menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center|top">
<ScrollView
android:id="@+id/tv_pip_menu_scroll"
@@ -92,7 +93,6 @@
android:id="@+id/tv_pip_menu_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0"
android:layout_margin="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_border"/>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2022 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.
-->
<!-- Layout for the back surface of the PiP menu -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_background"
android:elevation="@dimen/pip_menu_elevation"/>
</FrameLayout>

View File

@@ -22,7 +22,12 @@
<dimen name="pip_menu_button_margin">4dp</dimen>
<dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
<dimen name="pip_menu_border_width">4dp</dimen>
<dimen name="pip_menu_border_radius">4dp</dimen>
<integer name="pip_menu_fade_animation_duration">500</integer>
<!-- The pip menu front border corner radius is 2dp smaller than
the background corner radius to hide the background from
showing through. -->
<dimen name="pip_menu_border_corner_radius">4dp</dimen>
<dimen name="pip_menu_background_corner_radius">6dp</dimen>
<dimen name="pip_menu_outer_space">24dp</dimen>
<!-- outer space minus border width -->
@@ -30,5 +35,7 @@
<dimen name="pip_menu_arrow_size">24dp</dimen>
<dimen name="pip_menu_arrow_elevation">5dp</dimen>
<dimen name="pip_menu_elevation">1dp</dimen>
</resources>

View File

@@ -23,4 +23,5 @@
<color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
<color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color>
<color name="tv_pip_menu_focus_border">#E8EAED</color>
</resources>
<color name="tv_pip_menu_background">#1E232C</color>
</resources>

View File

@@ -27,6 +27,7 @@ import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_VERTICAL;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.ArraySet;
@@ -150,6 +151,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
mKeepClearAlgorithm.setScreenSize(screenSize);
mKeepClearAlgorithm.setMovementBounds(insetBounds);
mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset());
mKeepClearAlgorithm.setPipDecorInsets(mTvPipBoundsState.getPipMenuPermanentDecorInsets());
final Placement placement = mKeepClearAlgorithm.calculatePipPosition(
pipSize,
@@ -340,6 +342,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
final DisplayLayout displayLayout = mTvPipBoundsState.getDisplayLayout();
final float expandedRatio =
mTvPipBoundsState.getDesiredTvExpandedAspectRatio(); // width / height
final Insets pipDecorations = mTvPipBoundsState.getPipMenuPermanentDecorInsets();
final Size expandedSize;
if (expandedRatio == 0) {
@@ -352,7 +355,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y);
int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y)
- pipDecorations.top - pipDecorations.bottom;
float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
if (maxHeight > aspectRatioHeight) {
@@ -374,7 +378,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x);
int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x)
- pipDecorations.left - pipDecorations.right;
float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
if (maxWidth > aspectRatioWidth) {
if (DEBUG) {

View File

@@ -24,6 +24,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Insets;
import android.util.Size;
import android.view.Gravity;
@@ -60,7 +61,7 @@ public class TvPipBoundsState extends PipBoundsState {
private @Orientation int mTvFixedPipOrientation;
private int mTvPipGravity;
private @Nullable Size mTvExpandedSize;
private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE;
public TvPipBoundsState(@NonNull Context context) {
super(context);
@@ -159,4 +160,11 @@ public class TvPipBoundsState extends PipBoundsState {
return mIsTvExpandedPipSupported;
}
public void setPipMenuPermanentDecorInsets(@NonNull Insets permanentInsets) {
mPipMenuPermanentDecorInsets = permanentInsets;
}
public @NonNull Insets getPipMenuPermanentDecorInsets() {
return mPipMenuPermanentDecorInsets;
}
}

View File

@@ -16,6 +16,7 @@
package com.android.wm.shell.pip.tv
import android.graphics.Insets
import android.graphics.Point
import android.graphics.Rect
import android.util.Size
@@ -94,6 +95,9 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet()
private var lastStashTime: Long = Long.MIN_VALUE
/** Spaces around the PiP that we should leave space for when placing the PiP */
private var pipDecorInsets = Insets.NONE
/**
* Calculates the position the PiP should be placed at, taking into consideration the
* given keep clear areas.
@@ -120,7 +124,9 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
): Placement {
val transformedRestrictedAreas = transformAndFilterAreas(restrictedAreas)
val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas)
val pipAnchorBounds = getNormalPipAnchorBounds(pipSize, transformedMovementBounds)
val pipSizeWithDecors = addDecors(pipSize)
val pipAnchorBounds = getNormalPipAnchorBounds(pipSizeWithDecors, transformedMovementBounds)
val result = calculatePipPositionTransformed(
pipAnchorBounds,
@@ -128,12 +134,17 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
transformedUnrestrictedAreas
)
val screenSpaceBounds = fromTransformedSpace(result.bounds)
val pipBounds = removeDecors(fromTransformedSpace(result.bounds))
val anchorBounds = removeDecors(fromTransformedSpace(result.anchorBounds))
val unstashedDestBounds = result.unstashDestinationBounds?.let {
removeDecors(fromTransformedSpace(it))
}
return Placement(
screenSpaceBounds,
fromTransformedSpace(result.anchorBounds),
getStashType(screenSpaceBounds, movementBounds),
result.unstashDestinationBounds?.let { fromTransformedSpace(it) },
pipBounds,
anchorBounds,
getStashType(pipBounds, movementBounds),
unstashedDestBounds,
result.unstashTime
)
}
@@ -447,6 +458,11 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
transformedMovementBounds = toTransformedSpace(movementBounds)
}
fun setPipDecorInsets(insets: Insets) {
if (pipDecorInsets == insets) return
pipDecorInsets = insets
}
/**
* @param open Whether this event marks the opening of an occupied segment
* @param pos The coordinate of this event
@@ -735,6 +751,25 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
return horizontal && vertical
}
/**
* Adds space around [size] to leave space for decorations that will be drawn around the pip
*/
private fun addDecors(size: Size): Size {
val bounds = Rect(0, 0, size.width, size.height)
bounds.inset(pipDecorInsets)
return Size(bounds.width(), bounds.height())
}
/**
* Removes the space that was reserved for decorations around the pip
*/
private fun removeDecors(bounds: Rect): Rect {
val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipDecorInsets)
bounds.inset(pipDecorReverseInsets)
return bounds
}
private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) }
private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom
private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right

View File

@@ -25,13 +25,17 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
import android.os.RemoteException;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SyncRtSurfaceTransactionApplier;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
import androidx.annotation.Nullable;
@@ -53,6 +57,7 @@ import java.util.Objects;
public class TvPipMenuController implements PipMenuController, TvPipMenuView.Listener {
private static final String TAG = "TvPipMenuController";
private static final boolean DEBUG = TvPipController.DEBUG;
private static final String BACKGROUND_WINDOW_TITLE = "PipBackgroundView";
private final Context mContext;
private final SystemWindows mSystemWindows;
@@ -62,6 +67,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private Delegate mDelegate;
private SurfaceControl mLeash;
private TvPipMenuView mPipMenuView;
private View mPipBackgroundView;
// User can actively move the PiP via the DPAD.
private boolean mInMoveMode;
@@ -74,6 +80,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private RemoteAction mCloseAction;
private SyncRtSurfaceTransactionApplier mApplier;
private SyncRtSurfaceTransactionApplier mBackgroundApplier;
RectF mTmpSourceRectF = new RectF();
RectF mTmpDestinationRectF = new RectF();
Matrix mMoveTransform = new Matrix();
@@ -91,6 +98,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (DEBUG) e.printStackTrace();
}
};
private final int mPipMenuSurfaceOuterSpace;
public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
SystemWindows systemWindows, PipMediaController pipMediaController,
@@ -113,6 +121,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mainHandler, Context.RECEIVER_EXPORTED);
pipMediaController.addActionListener(this::onMediaActionsChanged);
mPipMenuSurfaceOuterSpace = context.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_outer_space);
final int pipBorderWidth = context.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_border_width);
mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-pipBorderWidth,
-pipBorderWidth, -pipBorderWidth, -pipBorderWidth));
}
void setDelegate(Delegate delegate) {
@@ -138,24 +154,54 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
mLeash = leash;
attachPipMenu();
}
private void attachPipMenu() {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: attachPipMenu()", TAG);
}
if (mPipMenuView != null) {
detachPipMenu();
}
attachPipBackgroundView();
attachPipMenuView();
}
private void attachPipMenuView() {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: attachPipMenuView()", TAG);
}
if (mPipMenuView != null) {
detachPipMenuView();
}
mPipMenuView = new TvPipMenuView(mContext);
mPipMenuView.setListener(this);
mSystemWindows.addView(mPipMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
0, SHELL_ROOT_LAYER_PIP);
setUpViewSurfaceZOrder(mPipMenuView, 1);
addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
}
private void attachPipBackgroundView() {
mPipBackgroundView = LayoutInflater.from(mContext)
.inflate(R.layout.tv_pip_menu_background, null);
setUpViewSurfaceZOrder(mPipBackgroundView, -1);
addPipMenuViewToSystemWindows(mPipBackgroundView, BACKGROUND_WINDOW_TITLE);
}
private void setUpViewSurfaceZOrder(View v, int zOrderRelativeToPip) {
v.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
v.getViewRootImpl().addSurfaceChangedCallback(
new PipMenuSurfaceChangedCallback(v, zOrderRelativeToPip));
}
@Override
public void onViewDetachedFromWindow(View v) {
}
});
}
private void addPipMenuViewToSystemWindows(View v, String title) {
mSystemWindows.addView(v, getPipMenuLayoutParams(title, 0 /* width */, 0 /* height */),
0 /* displayId */, SHELL_ROOT_LAYER_PIP);
}
void showMovementMenuOnly() {
@@ -171,8 +217,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override
public void showMenu() {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showMenu()", TAG);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
}
mInMoveMode = false;
mCloseAfterExitMoveMenu = false;
@@ -183,24 +228,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (mPipMenuView == null) {
return;
}
Rect menuBounds = getMenuBounds(mTvPipBoundsState.getBounds());
mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams(
MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height()));
maybeUpdateMenuViewActions();
updateExpansionState();
SurfaceControl menuSurfaceControl = getSurfaceControl();
if (menuSurfaceControl != null) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
t.setRelativeLayer(mPipMenuView.getWindowSurfaceControl(), mLeash, 1);
t.setPosition(menuSurfaceControl, menuBounds.left, menuBounds.top);
t.apply();
}
grantPipMenuFocus(true);
if (mInMoveMode) {
mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
} else {
mPipMenuView.showButtonMenu();
mPipMenuView.showButtonsMenu();
}
}
@@ -214,11 +249,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mPipMenuView.setIsExpanded(mTvPipBoundsState.isTvPipExpanded());
}
private Rect getMenuBounds(Rect pipBounds) {
int extraSpaceInPx = mContext.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_outer_space);
private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
Rect menuBounds = new Rect(pipBounds);
menuBounds.inset(-extraSpaceInPx, -extraSpaceInPx);
menuBounds.inset(-mPipMenuSurfaceOuterSpace, -mPipMenuSurfaceOuterSpace);
return menuBounds;
}
@@ -227,11 +260,12 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: closeMenu()", TAG);
}
if (mPipMenuView == null) {
return;
}
mPipMenuView.hideAll();
mPipMenuView.hideAllUserControls();
grantPipMenuFocus(false);
mDelegate.onMenuClosed();
}
@@ -266,7 +300,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
if (mInMoveMode) {
mInMoveMode = false;
mPipMenuView.showButtonMenu();
mPipMenuView.showButtonsMenu();
return true;
}
return false;
@@ -287,7 +321,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override
public void detach() {
closeMenu();
detachPipMenuView();
detachPipMenu();
mLeash = null;
}
@@ -346,20 +380,15 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override
public boolean isMenuVisible() {
boolean isVisible = mPipMenuView != null && mPipMenuView.isVisible();
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: isMenuVisible: %b", TAG, isVisible);
}
return isVisible;
return true;
}
/**
* Does an immediate window crop of the PiP menu.
*/
@Override
public void resizePipMenu(@android.annotation.Nullable SurfaceControl pipLeash,
@android.annotation.Nullable SurfaceControl.Transaction t,
public void resizePipMenu(@Nullable SurfaceControl pipLeash,
@Nullable SurfaceControl.Transaction t,
Rect destinationBounds) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -373,24 +402,36 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
return;
}
SurfaceControl surfaceControl = getSurfaceControl();
SyncRtSurfaceTransactionApplier.SurfaceParams
params = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(surfaceControl)
.withWindowCrop(getMenuBounds(destinationBounds))
final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds);
final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams =
new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface)
.withWindowCrop(menuBounds)
.build();
final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
final SyncRtSurfaceTransactionApplier.SurfaceParams backParams =
new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(backSurface)
.withWindowCrop(menuBounds)
.build();
// TODO(b/226580399): switch to using SurfaceSyncer (see b/200284684) to synchronize the
// animations of the pip surface with the content of the front and back menu surfaces
mBackgroundApplier.scheduleApply(backParams);
if (pipLeash != null && t != null) {
SyncRtSurfaceTransactionApplier.SurfaceParams
final SyncRtSurfaceTransactionApplier.SurfaceParams
pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
.withMergeTransaction(t)
.build();
mApplier.scheduleApply(params, pipParams);
mApplier.scheduleApply(frontParams, pipParams);
} else {
mApplier.scheduleApply(params);
mApplier.scheduleApply(frontParams);
}
}
private SurfaceControl getSurfaceControl() {
return mSystemWindows.getViewSurface(mPipMenuView);
private SurfaceControl getSurfaceControl(View v) {
return mSystemWindows.getViewSurface(v);
}
@Override
@@ -412,44 +453,52 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
return;
}
Rect menuDestBounds = getMenuBounds(pipDestBounds);
Rect mTmpSourceBounds = new Rect();
final Rect menuDestBounds = calculateMenuSurfaceBounds(pipDestBounds);
final Rect tmpSourceBounds = new Rect();
// If there is no pip leash supplied, that means the PiP leash is already finalized
// resizing and the PiP menu is also resized. We then want to do a scale from the current
// new menu bounds.
if (pipLeash != null && transaction != null) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: mTmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG);
"%s: tmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG);
}
mPipMenuView.getBoundsOnScreen(mTmpSourceBounds);
mPipMenuView.getBoundsOnScreen(tmpSourceBounds);
} else {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: mTmpSourceBounds based on menu width and height", TAG);
"%s: tmpSourceBounds based on menu width and height", TAG);
}
mTmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
tmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
}
mTmpSourceRectF.set(mTmpSourceBounds);
mTmpSourceRectF.set(tmpSourceBounds);
mTmpDestinationRectF.set(menuDestBounds);
mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
mMoveTransform.setTranslate(mTmpDestinationRectF.left, mTmpDestinationRectF.top);
SurfaceControl surfaceControl = getSurfaceControl();
SyncRtSurfaceTransactionApplier.SurfaceParams params =
new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
surfaceControl)
final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams =
new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface)
.withMatrix(mMoveTransform)
.build();
final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
final SyncRtSurfaceTransactionApplier.SurfaceParams backParams =
new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(backSurface)
.withMatrix(mMoveTransform)
.build();
// TODO(b/226580399): switch to using SurfaceSyncer (see b/200284684) to synchronize the
// animations of the pip surface with the content of the front and back menu surfaces
mBackgroundApplier.scheduleApply(backParams);
if (pipLeash != null && transaction != null) {
SyncRtSurfaceTransactionApplier.SurfaceParams
pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
final SyncRtSurfaceTransactionApplier.SurfaceParams pipParams =
new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
.withMergeTransaction(transaction)
.build();
mApplier.scheduleApply(params, pipParams);
mApplier.scheduleApply(frontParams, pipParams);
} else {
mApplier.scheduleApply(params);
mApplier.scheduleApply(frontParams);
}
if (mPipMenuView.getViewRootImpl() != null) {
@@ -470,29 +519,40 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (mApplier == null) {
mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
}
if (mBackgroundApplier == null) {
mBackgroundApplier = new SyncRtSurfaceTransactionApplier(mPipBackgroundView);
}
return true;
}
private void detachPipMenuView() {
if (mPipMenuView == null) {
return;
private void detachPipMenu() {
if (mPipMenuView != null) {
mApplier = null;
mSystemWindows.removeView(mPipMenuView);
mPipMenuView = null;
}
mApplier = null;
mSystemWindows.removeView(mPipMenuView);
mPipMenuView = null;
if (mPipBackgroundView != null) {
mBackgroundApplier = null;
mSystemWindows.removeView(mPipBackgroundView);
mPipBackgroundView = null;
}
}
@Override
public void updateMenuBounds(Rect destinationBounds) {
Rect menuBounds = getMenuBounds(destinationBounds);
final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds);
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: updateMenuBounds: %s", TAG, menuBounds.toShortString());
}
mSystemWindows.updateViewLayout(mPipBackgroundView,
getPipMenuLayoutParams(BACKGROUND_WINDOW_TITLE, menuBounds.width(),
menuBounds.height()));
mSystemWindows.updateViewLayout(mPipMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(),
menuBounds.height()));
if (mPipMenuView != null) {
mPipMenuView.updateLayout(destinationBounds);
}
@@ -555,4 +615,30 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
"%s: Unable to update focus, %s", TAG, e);
}
}
private class PipMenuSurfaceChangedCallback implements ViewRootImpl.SurfaceChangedCallback {
private final View mView;
private final int mZOrder;
PipMenuSurfaceChangedCallback(View v, int zOrder) {
mView = v;
mZOrder = zOrder;
}
@Override
public void surfaceCreated(SurfaceControl.Transaction t) {
final SurfaceControl sc = getSurfaceControl(mView);
if (sc != null) {
t.setRelativeLayer(sc, mLeash, mZOrder);
}
}
@Override
public void surfaceReplaced(SurfaceControl.Transaction t) {
}
@Override
public void surfaceDestroyed() {
}
}
}

View File

@@ -81,6 +81,8 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private final TvPipMenuActionButton mExpandButton;
private final TvPipMenuActionButton mCloseButton;
private final int mPipMenuFadeAnimationDuration;
public TvPipMenuView(@NonNull Context context) {
this(context, null);
}
@@ -121,6 +123,9 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left);
mPipMenuFadeAnimationDuration = context.getResources()
.getInteger(R.integer.pip_menu_fade_animation_duration);
}
void updateLayout(Rect updatedBounds) {
@@ -184,30 +189,32 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
}
showMenuButtons(false);
showButtonsMenu(false);
showMovementHints(gravity);
showMenuFrame(true);
setFrameHighlighted(true);
}
void showButtonMenu() {
void showButtonsMenu() {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showButtonMenu()", TAG);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showButtonsMenu()", TAG);
}
showMenuButtons(true);
showButtonsMenu(true);
hideMovementHints();
showMenuFrame(true);
setFrameHighlighted(true);
}
/**
* Hides all menu views, including the menu frame.
*/
void hideAll() {
void hideAllUserControls() {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: hideAll()", TAG);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: hideAllUserControls()", TAG);
}
showMenuButtons(false);
showButtonsMenu(false);
hideMovementHints();
showMenuFrame(false);
setFrameHighlighted(false);
}
private void animateAlphaTo(float alpha, View view) {
@@ -217,7 +224,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
view.animate()
.alpha(alpha)
.setInterpolator(alpha == 0f ? TvPipInterpolators.EXIT : TvPipInterpolators.ENTER)
.setDuration(500)
.setDuration(mPipMenuFadeAnimationDuration)
.withStartAction(() -> {
if (alpha != 0) {
view.setVisibility(VISIBLE);
@@ -230,15 +237,6 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
});
}
boolean isVisible() {
return mMenuFrameView.getAlpha() != 0f
|| mActionButtonsContainer.getAlpha() != 0f
|| mArrowUp.getAlpha() != 0f
|| mArrowRight.getAlpha() != 0f
|| mArrowDown.getAlpha() != 0f
|| mArrowLeft.getAlpha() != 0f;
}
void setAdditionalActions(List<RemoteAction> actions, RemoteAction closeAction,
Handler mainHandler) {
if (DEBUG) {
@@ -423,18 +421,18 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
}
/**
* Show or hide the pip user actions.
* Show or hide the pip buttons menu.
*/
public void showMenuButtons(boolean show) {
public void showButtonsMenu(boolean show) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showMenuButtons: %b", TAG, show);
"%s: showUserActions: %b", TAG, show);
}
animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
}
private void showMenuFrame(boolean show) {
animateAlphaTo(show ? 1 : 0, mMenuFrameView);
private void setFrameHighlighted(boolean highlighted) {
mMenuFrameView.setActivated(highlighted);
}
interface Listener {