Merge "Add Tv Pip rounded corners tray" into tm-dev

This commit is contained in:
Galia Peycheva
2022-04-05 05:59:14 +00:00
committed by Android (Google) Code Review
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 ~ See the License for the specific language governing permissions and
~ limitations under the License. ~ limitations under the License.
--> -->
<shape xmlns:android="http://schemas.android.com/apk/res/android" <selector xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:exitFadeDuration="@integer/pip_menu_fade_animation_duration">
<corners android:radius="@dimen/pip_menu_border_radius" /> <item android:state_activated="true">
<stroke android:width="@dimen/pip_menu_border_width" <shape android:shape="rectangle">
android:color="@color/tv_pip_menu_focus_border" /> <corners android:radius="@dimen/pip_menu_border_corner_radius" />
</shape> <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 --> <!-- Layout for TvPipMenuView -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_pip_menu" android:id="@+id/tv_pip_menu"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:gravity="center|top">
<ScrollView <ScrollView
android:id="@+id/tv_pip_menu_scroll" android:id="@+id/tv_pip_menu_scroll"
@@ -92,7 +93,6 @@
android:id="@+id/tv_pip_menu_frame" android:id="@+id/tv_pip_menu_frame"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:alpha="0"
android:layout_margin="@dimen/pip_menu_outer_space_frame" android:layout_margin="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_border"/> 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_margin">4dp</dimen>
<dimen name="pip_menu_button_wrapper_margin">26dp</dimen> <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
<dimen name="pip_menu_border_width">4dp</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> <dimen name="pip_menu_outer_space">24dp</dimen>
<!-- outer space minus border width --> <!-- outer space minus border width -->
@@ -30,5 +35,7 @@
<dimen name="pip_menu_arrow_size">24dp</dimen> <dimen name="pip_menu_arrow_size">24dp</dimen>
<dimen name="pip_menu_arrow_elevation">5dp</dimen> <dimen name="pip_menu_arrow_elevation">5dp</dimen>
<dimen name="pip_menu_elevation">1dp</dimen>
</resources> </resources>

View File

@@ -23,4 +23,5 @@
<color name="tv_pip_menu_icon_bg_focused">#E8EAED</color> <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_icon_bg_unfocused">#990E0E0F</color>
<color name="tv_pip_menu_focus_border">#E8EAED</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.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.ArraySet; import android.util.ArraySet;
@@ -150,6 +151,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
mKeepClearAlgorithm.setScreenSize(screenSize); mKeepClearAlgorithm.setScreenSize(screenSize);
mKeepClearAlgorithm.setMovementBounds(insetBounds); mKeepClearAlgorithm.setMovementBounds(insetBounds);
mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset()); mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset());
mKeepClearAlgorithm.setPipDecorInsets(mTvPipBoundsState.getPipMenuPermanentDecorInsets());
final Placement placement = mKeepClearAlgorithm.calculatePipPosition( final Placement placement = mKeepClearAlgorithm.calculatePipPosition(
pipSize, pipSize,
@@ -340,6 +342,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
final DisplayLayout displayLayout = mTvPipBoundsState.getDisplayLayout(); final DisplayLayout displayLayout = mTvPipBoundsState.getDisplayLayout();
final float expandedRatio = final float expandedRatio =
mTvPipBoundsState.getDesiredTvExpandedAspectRatio(); // width / height mTvPipBoundsState.getDesiredTvExpandedAspectRatio(); // width / height
final Insets pipDecorations = mTvPipBoundsState.getPipMenuPermanentDecorInsets();
final Size expandedSize; final Size expandedSize;
if (expandedRatio == 0) { if (expandedRatio == 0) {
@@ -352,7 +355,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) { if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize(); expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else { } else {
int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y); int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y)
- pipDecorations.top - pipDecorations.bottom;
float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio; float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
if (maxHeight > aspectRatioHeight) { if (maxHeight > aspectRatioHeight) {
@@ -374,7 +378,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) { if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize(); expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else { } else {
int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x); int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x)
- pipDecorations.left - pipDecorations.right;
float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio; float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
if (maxWidth > aspectRatioWidth) { if (maxWidth > aspectRatioWidth) {
if (DEBUG) { if (DEBUG) {

View File

@@ -24,6 +24,7 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Insets;
import android.util.Size; import android.util.Size;
import android.view.Gravity; import android.view.Gravity;
@@ -60,7 +61,7 @@ public class TvPipBoundsState extends PipBoundsState {
private @Orientation int mTvFixedPipOrientation; private @Orientation int mTvFixedPipOrientation;
private int mTvPipGravity; private int mTvPipGravity;
private @Nullable Size mTvExpandedSize; private @Nullable Size mTvExpandedSize;
private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE;
public TvPipBoundsState(@NonNull Context context) { public TvPipBoundsState(@NonNull Context context) {
super(context); super(context);
@@ -159,4 +160,11 @@ public class TvPipBoundsState extends PipBoundsState {
return mIsTvExpandedPipSupported; 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 package com.android.wm.shell.pip.tv
import android.graphics.Insets
import android.graphics.Point import android.graphics.Point
import android.graphics.Rect import android.graphics.Rect
import android.util.Size import android.util.Size
@@ -94,6 +95,9 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet() private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet()
private var lastStashTime: Long = Long.MIN_VALUE 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 * Calculates the position the PiP should be placed at, taking into consideration the
* given keep clear areas. * given keep clear areas.
@@ -120,7 +124,9 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
): Placement { ): Placement {
val transformedRestrictedAreas = transformAndFilterAreas(restrictedAreas) val transformedRestrictedAreas = transformAndFilterAreas(restrictedAreas)
val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas) val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas)
val pipAnchorBounds = getNormalPipAnchorBounds(pipSize, transformedMovementBounds)
val pipSizeWithDecors = addDecors(pipSize)
val pipAnchorBounds = getNormalPipAnchorBounds(pipSizeWithDecors, transformedMovementBounds)
val result = calculatePipPositionTransformed( val result = calculatePipPositionTransformed(
pipAnchorBounds, pipAnchorBounds,
@@ -128,12 +134,17 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
transformedUnrestrictedAreas 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( return Placement(
screenSpaceBounds, pipBounds,
fromTransformedSpace(result.anchorBounds), anchorBounds,
getStashType(screenSpaceBounds, movementBounds), getStashType(pipBounds, movementBounds),
result.unstashDestinationBounds?.let { fromTransformedSpace(it) }, unstashedDestBounds,
result.unstashTime result.unstashTime
) )
} }
@@ -447,6 +458,11 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
transformedMovementBounds = toTransformedSpace(movementBounds) 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 open Whether this event marks the opening of an occupied segment
* @param pos The coordinate of this event * @param pos The coordinate of this event
@@ -735,6 +751,25 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
return horizontal && vertical 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.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.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom
private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right 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.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.ParceledListSlice; import android.content.pm.ParceledListSlice;
import android.graphics.Insets;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.RectF; import android.graphics.RectF;
import android.os.Handler; import android.os.Handler;
import android.os.RemoteException; import android.os.RemoteException;
import android.view.LayoutInflater;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.SyncRtSurfaceTransactionApplier; import android.view.SyncRtSurfaceTransactionApplier;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal; import android.view.WindowManagerGlobal;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -53,6 +57,7 @@ import java.util.Objects;
public class TvPipMenuController implements PipMenuController, TvPipMenuView.Listener { public class TvPipMenuController implements PipMenuController, TvPipMenuView.Listener {
private static final String TAG = "TvPipMenuController"; private static final String TAG = "TvPipMenuController";
private static final boolean DEBUG = TvPipController.DEBUG; private static final boolean DEBUG = TvPipController.DEBUG;
private static final String BACKGROUND_WINDOW_TITLE = "PipBackgroundView";
private final Context mContext; private final Context mContext;
private final SystemWindows mSystemWindows; private final SystemWindows mSystemWindows;
@@ -62,6 +67,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private Delegate mDelegate; private Delegate mDelegate;
private SurfaceControl mLeash; private SurfaceControl mLeash;
private TvPipMenuView mPipMenuView; private TvPipMenuView mPipMenuView;
private View mPipBackgroundView;
// User can actively move the PiP via the DPAD. // User can actively move the PiP via the DPAD.
private boolean mInMoveMode; private boolean mInMoveMode;
@@ -74,6 +80,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private RemoteAction mCloseAction; private RemoteAction mCloseAction;
private SyncRtSurfaceTransactionApplier mApplier; private SyncRtSurfaceTransactionApplier mApplier;
private SyncRtSurfaceTransactionApplier mBackgroundApplier;
RectF mTmpSourceRectF = new RectF(); RectF mTmpSourceRectF = new RectF();
RectF mTmpDestinationRectF = new RectF(); RectF mTmpDestinationRectF = new RectF();
Matrix mMoveTransform = new Matrix(); Matrix mMoveTransform = new Matrix();
@@ -91,6 +98,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (DEBUG) e.printStackTrace(); if (DEBUG) e.printStackTrace();
} }
}; };
private final int mPipMenuSurfaceOuterSpace;
public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState, public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
SystemWindows systemWindows, PipMediaController pipMediaController, SystemWindows systemWindows, PipMediaController pipMediaController,
@@ -113,6 +121,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mainHandler, Context.RECEIVER_EXPORTED); mainHandler, Context.RECEIVER_EXPORTED);
pipMediaController.addActionListener(this::onMediaActionsChanged); 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) { void setDelegate(Delegate delegate) {
@@ -138,24 +154,54 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
} }
mLeash = leash; 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(); attachPipMenuView();
} }
private void 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 = new TvPipMenuView(mContext);
mPipMenuView.setListener(this); mPipMenuView.setListener(this);
mSystemWindows.addView(mPipMenuView, setUpViewSurfaceZOrder(mPipMenuView, 1);
getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
0, SHELL_ROOT_LAYER_PIP); }
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() { void showMovementMenuOnly() {
@@ -171,8 +217,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override @Override
public void showMenu() { public void showMenu() {
if (DEBUG) { if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
"%s: showMenu()", TAG);
} }
mInMoveMode = false; mInMoveMode = false;
mCloseAfterExitMoveMenu = false; mCloseAfterExitMoveMenu = false;
@@ -183,24 +228,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (mPipMenuView == null) { if (mPipMenuView == null) {
return; return;
} }
Rect menuBounds = getMenuBounds(mTvPipBoundsState.getBounds());
mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams(
MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height()));
maybeUpdateMenuViewActions(); maybeUpdateMenuViewActions();
updateExpansionState(); 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); grantPipMenuFocus(true);
if (mInMoveMode) { if (mInMoveMode) {
mPipMenuView.showMoveMenu(mDelegate.getPipGravity()); mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
} else { } else {
mPipMenuView.showButtonMenu(); mPipMenuView.showButtonsMenu();
} }
} }
@@ -214,11 +249,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mPipMenuView.setIsExpanded(mTvPipBoundsState.isTvPipExpanded()); mPipMenuView.setIsExpanded(mTvPipBoundsState.isTvPipExpanded());
} }
private Rect getMenuBounds(Rect pipBounds) { private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
int extraSpaceInPx = mContext.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_outer_space);
Rect menuBounds = new Rect(pipBounds); Rect menuBounds = new Rect(pipBounds);
menuBounds.inset(-extraSpaceInPx, -extraSpaceInPx); menuBounds.inset(-mPipMenuSurfaceOuterSpace, -mPipMenuSurfaceOuterSpace);
return menuBounds; return menuBounds;
} }
@@ -227,11 +260,12 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: closeMenu()", TAG); "%s: closeMenu()", TAG);
} }
if (mPipMenuView == null) { if (mPipMenuView == null) {
return; return;
} }
mPipMenuView.hideAll(); mPipMenuView.hideAllUserControls();
grantPipMenuFocus(false); grantPipMenuFocus(false);
mDelegate.onMenuClosed(); mDelegate.onMenuClosed();
} }
@@ -266,7 +300,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
} }
if (mInMoveMode) { if (mInMoveMode) {
mInMoveMode = false; mInMoveMode = false;
mPipMenuView.showButtonMenu(); mPipMenuView.showButtonsMenu();
return true; return true;
} }
return false; return false;
@@ -287,7 +321,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override @Override
public void detach() { public void detach() {
closeMenu(); closeMenu();
detachPipMenuView(); detachPipMenu();
mLeash = null; mLeash = null;
} }
@@ -346,20 +380,15 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override @Override
public boolean isMenuVisible() { public boolean isMenuVisible() {
boolean isVisible = mPipMenuView != null && mPipMenuView.isVisible(); return true;
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: isMenuVisible: %b", TAG, isVisible);
}
return isVisible;
} }
/** /**
* Does an immediate window crop of the PiP menu. * Does an immediate window crop of the PiP menu.
*/ */
@Override @Override
public void resizePipMenu(@android.annotation.Nullable SurfaceControl pipLeash, public void resizePipMenu(@Nullable SurfaceControl pipLeash,
@android.annotation.Nullable SurfaceControl.Transaction t, @Nullable SurfaceControl.Transaction t,
Rect destinationBounds) { Rect destinationBounds) {
if (DEBUG) { if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -373,24 +402,36 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
return; return;
} }
SurfaceControl surfaceControl = getSurfaceControl(); final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds);
SyncRtSurfaceTransactionApplier.SurfaceParams
params = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(surfaceControl) final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
.withWindowCrop(getMenuBounds(destinationBounds)) final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams =
new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface)
.withWindowCrop(menuBounds)
.build(); .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) { if (pipLeash != null && t != null) {
SyncRtSurfaceTransactionApplier.SurfaceParams final SyncRtSurfaceTransactionApplier.SurfaceParams
pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash) pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
.withMergeTransaction(t) .withMergeTransaction(t)
.build(); .build();
mApplier.scheduleApply(params, pipParams); mApplier.scheduleApply(frontParams, pipParams);
} else { } else {
mApplier.scheduleApply(params); mApplier.scheduleApply(frontParams);
} }
} }
private SurfaceControl getSurfaceControl() { private SurfaceControl getSurfaceControl(View v) {
return mSystemWindows.getViewSurface(mPipMenuView); return mSystemWindows.getViewSurface(v);
} }
@Override @Override
@@ -412,44 +453,52 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
return; return;
} }
Rect menuDestBounds = getMenuBounds(pipDestBounds); final Rect menuDestBounds = calculateMenuSurfaceBounds(pipDestBounds);
Rect mTmpSourceBounds = new Rect(); final Rect tmpSourceBounds = new Rect();
// If there is no pip leash supplied, that means the PiP leash is already finalized // 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 // resizing and the PiP menu is also resized. We then want to do a scale from the current
// new menu bounds. // new menu bounds.
if (pipLeash != null && transaction != null) { if (pipLeash != null && transaction != null) {
if (DEBUG) { if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 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 { } else {
if (DEBUG) { if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 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); mTmpDestinationRectF.set(menuDestBounds);
mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); mMoveTransform.setTranslate(mTmpDestinationRectF.left, mTmpDestinationRectF.top);
SurfaceControl surfaceControl = getSurfaceControl(); final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
SyncRtSurfaceTransactionApplier.SurfaceParams params = final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams =
new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface)
surfaceControl)
.withMatrix(mMoveTransform) .withMatrix(mMoveTransform)
.build(); .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) { if (pipLeash != null && transaction != null) {
SyncRtSurfaceTransactionApplier.SurfaceParams final SyncRtSurfaceTransactionApplier.SurfaceParams pipParams =
pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash) new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
.withMergeTransaction(transaction) .withMergeTransaction(transaction)
.build(); .build();
mApplier.scheduleApply(params, pipParams); mApplier.scheduleApply(frontParams, pipParams);
} else { } else {
mApplier.scheduleApply(params); mApplier.scheduleApply(frontParams);
} }
if (mPipMenuView.getViewRootImpl() != null) { if (mPipMenuView.getViewRootImpl() != null) {
@@ -470,29 +519,40 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (mApplier == null) { if (mApplier == null) {
mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView); mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
} }
if (mBackgroundApplier == null) {
mBackgroundApplier = new SyncRtSurfaceTransactionApplier(mPipBackgroundView);
}
return true; return true;
} }
private void detachPipMenuView() { private void detachPipMenu() {
if (mPipMenuView == null) { if (mPipMenuView != null) {
return; mApplier = null;
mSystemWindows.removeView(mPipMenuView);
mPipMenuView = null;
} }
mApplier = null; if (mPipBackgroundView != null) {
mSystemWindows.removeView(mPipMenuView); mBackgroundApplier = null;
mPipMenuView = null; mSystemWindows.removeView(mPipBackgroundView);
mPipBackgroundView = null;
}
} }
@Override @Override
public void updateMenuBounds(Rect destinationBounds) { public void updateMenuBounds(Rect destinationBounds) {
Rect menuBounds = getMenuBounds(destinationBounds); final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds);
if (DEBUG) { if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: updateMenuBounds: %s", TAG, menuBounds.toShortString()); "%s: updateMenuBounds: %s", TAG, menuBounds.toShortString());
} }
mSystemWindows.updateViewLayout(mPipBackgroundView,
getPipMenuLayoutParams(BACKGROUND_WINDOW_TITLE, menuBounds.width(),
menuBounds.height()));
mSystemWindows.updateViewLayout(mPipMenuView, mSystemWindows.updateViewLayout(mPipMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(), getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(),
menuBounds.height())); menuBounds.height()));
if (mPipMenuView != null) { if (mPipMenuView != null) {
mPipMenuView.updateLayout(destinationBounds); mPipMenuView.updateLayout(destinationBounds);
} }
@@ -555,4 +615,30 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
"%s: Unable to update focus, %s", TAG, e); "%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 mExpandButton;
private final TvPipMenuActionButton mCloseButton; private final TvPipMenuActionButton mCloseButton;
private final int mPipMenuFadeAnimationDuration;
public TvPipMenuView(@NonNull Context context) { public TvPipMenuView(@NonNull Context context) {
this(context, null); this(context, null);
} }
@@ -121,6 +123,9 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right); mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down); mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left); mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left);
mPipMenuFadeAnimationDuration = context.getResources()
.getInteger(R.integer.pip_menu_fade_animation_duration);
} }
void updateLayout(Rect updatedBounds) { void updateLayout(Rect updatedBounds) {
@@ -184,30 +189,32 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
if (DEBUG) { if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG); ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
} }
showMenuButtons(false); showButtonsMenu(false);
showMovementHints(gravity); showMovementHints(gravity);
showMenuFrame(true); setFrameHighlighted(true);
} }
void showButtonMenu() { void showButtonsMenu() {
if (DEBUG) { 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(); hideMovementHints();
showMenuFrame(true); setFrameHighlighted(true);
} }
/** /**
* Hides all menu views, including the menu frame. * Hides all menu views, including the menu frame.
*/ */
void hideAll() { void hideAllUserControls() {
if (DEBUG) { 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(); hideMovementHints();
showMenuFrame(false); setFrameHighlighted(false);
} }
private void animateAlphaTo(float alpha, View view) { private void animateAlphaTo(float alpha, View view) {
@@ -217,7 +224,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
view.animate() view.animate()
.alpha(alpha) .alpha(alpha)
.setInterpolator(alpha == 0f ? TvPipInterpolators.EXIT : TvPipInterpolators.ENTER) .setInterpolator(alpha == 0f ? TvPipInterpolators.EXIT : TvPipInterpolators.ENTER)
.setDuration(500) .setDuration(mPipMenuFadeAnimationDuration)
.withStartAction(() -> { .withStartAction(() -> {
if (alpha != 0) { if (alpha != 0) {
view.setVisibility(VISIBLE); 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, void setAdditionalActions(List<RemoteAction> actions, RemoteAction closeAction,
Handler mainHandler) { Handler mainHandler) {
if (DEBUG) { 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) { if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showMenuButtons: %b", TAG, show); "%s: showUserActions: %b", TAG, show);
} }
animateAlphaTo(show ? 1 : 0, mActionButtonsContainer); animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
} }
private void showMenuFrame(boolean show) { private void setFrameHighlighted(boolean highlighted) {
animateAlphaTo(show ? 1 : 0, mMenuFrameView); mMenuFrameView.setActivated(highlighted);
} }
interface Listener { interface Listener {