Merge "Implement expanded PiP on TV."

This commit is contained in:
Galia Peycheva
2022-02-14 12:09:21 +00:00
committed by Android (Google) Code Review
25 changed files with 869 additions and 139 deletions

View File

@@ -61,4 +61,9 @@ oneway interface IPinnedTaskListener {
* Called by the window manager when the aspect ratio is reset.
*/
void onAspectRatioChanged(float aspectRatio);
/**
* Called by the window manager when the expanded aspect ratio is reset.
*/
void onExpandedAspectRatioChanged(float aspectRatio);
}

View File

@@ -37,6 +37,14 @@
Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
<integer name="config_defaultPictureInPictureGravity">0x55</integer>
<!-- The maximum height of the expanded horizontal picture-in-picture window -->
<item name="config_pictureInPictureExpandedHorizontalHeight"
format="dimension" type="dimen">110dp</item>
<!-- The maximum width of the expanded vertical picture-in-picture window -->
<item name="config_pictureInPictureExpandedVerticalWidth"
format="dimension" type="dimen">110dp</item>
<!-- Whether the device uses the default focus highlight when focus state isn't specified. -->
<bool name="config_useDefaultFocusHighlight">false</bool>

View File

@@ -415,6 +415,8 @@
<java-symbol type="dimen" name="config_pictureInPictureMinAspectRatio" />
<java-symbol type="dimen" name="config_pictureInPictureMaxAspectRatio" />
<java-symbol type="integer" name="config_pictureInPictureMaxNumberOfActions" />
<java-symbol type="dimen" name="config_pictureInPictureExpandedHorizontalHeight" />
<java-symbol type="dimen" name="config_pictureInPictureExpandedVerticalWidth" />
<java-symbol type="dimen" name="config_closeToSquareDisplayMaxAspectRatio" />
<java-symbol type="integer" name="config_burnInProtectionMinHorizontalOffset" />
<java-symbol type="integer" name="config_burnInProtectionMaxHorizontalOffset" />

View File

@@ -0,0 +1,25 @@
<!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/tv_pip_menu_focus_border"
android:pathData="M12,12V4h2v4.6L20.6,2 22,3.4 15.4,10H20v2zM3.4,22L2,20.6 8.6,14H4v-2h8v8h-2v-4.6z"/>
</vector>

View File

@@ -0,0 +1,25 @@
<!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/tv_pip_menu_focus_border"
android:pathData="M3,21v-8h2v4.6L17.6,5H13V3h8v8h-2V6.4L6.4,19H11v2z"/>
</vector>

View File

@@ -20,25 +20,36 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<HorizontalScrollView
<ScrollView
android:id="@+id/tv_pip_menu_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:gravity="center"
android:gravity="center_horizontal"
android:scrollbars="none"
android:layout_margin="@dimen/pip_menu_outer_space"
android:visibility="gone"/>
<HorizontalScrollView
android:id="@+id/tv_pip_menu_horizontal_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:scrollbars="none"
android:layout_centerInParent="true"
android:layout_margin="@dimen/pip_menu_outer_space">
<LinearLayout
android:id="@+id/tv_pip_menu_action_buttons"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingStart="@dimen/pip_menu_button_wrapper_margin"
android:paddingEnd="@dimen/pip_menu_button_wrapper_margin"
android:gravity="center"
android:layout_gravity="center"
android:orientation="horizontal"
android:alpha="0">
<Space
android:layout_width="@dimen/pip_menu_button_wrapper_margin"
android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
<com.android.wm.shell.pip.tv.TvPipMenuActionButton
android:id="@+id/tv_pip_menu_fullscreen_button"
android:layout_width="wrap_content"
@@ -53,6 +64,14 @@
android:src="@drawable/pip_ic_move_white"
android:text="@String/pip_move" />
<com.android.wm.shell.pip.tv.TvPipMenuActionButton
android:id="@+id/tv_pip_menu_expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/pip_ic_collapse"
android:visibility="gone"
android:text="@string/pip_collapse" />
<com.android.wm.shell.pip.tv.TvPipMenuActionButton
android:id="@+id/tv_pip_menu_close_button"
android:layout_width="wrap_content"
@@ -62,6 +81,10 @@
<!-- More TvPipMenuActionButtons may be added here at runtime. -->
<Space
android:layout_width="@dimen/pip_menu_button_wrapper_margin"
android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
</LinearLayout>
</HorizontalScrollView>

View File

@@ -20,8 +20,7 @@
android:id="@+id/button"
android:layout_width="@dimen/pip_menu_button_size"
android:layout_height="@dimen/pip_menu_button_size"
android:layout_marginStart="@dimen/pip_menu_button_margin"
android:layout_marginEnd="@dimen/pip_menu_button_margin"
android:layout_margin="@dimen/pip_menu_button_margin"
android:background="@drawable/tv_pip_button_bg"
android:focusable="true">

View File

@@ -33,5 +33,11 @@
<!-- Button to move picture-in-picture (PIP) via DPAD in the PIP menu [CHAR LIMIT=30] -->
<string name="pip_move">Move PIP</string>
<!-- Button to expand the picture-in-picture (PIP) window [CHAR LIMIT=30] -->
<string name="pip_expand">Expand PIP</string>
<!-- Button to collapse/shrink the picture-in-picture (PIP) window [CHAR LIMIT=30] -->
<string name="pip_collapse">Collapse PIP</string>
</resources>

View File

@@ -30,7 +30,6 @@ import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
@@ -39,6 +38,7 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
import com.android.wm.shell.pip.tv.TvPipBoundsState;
import com.android.wm.shell.pip.tv.TvPipController;
import com.android.wm.shell.pip.tv.TvPipMenuController;
import com.android.wm.shell.pip.tv.TvPipNotificationController;
@@ -60,7 +60,7 @@ public abstract class TvPipModule {
@Provides
static Optional<Pip> providePip(
Context context,
PipBoundsState pipBoundsState,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
TvPipMenuController tvPipMenuController,
@@ -73,7 +73,7 @@ public abstract class TvPipModule {
return Optional.of(
TvPipController.create(
context,
pipBoundsState,
tvPipBoundsState,
tvPipBoundsAlgorithm,
pipTaskOrganizer,
pipTransitionController,
@@ -94,14 +94,14 @@ public abstract class TvPipModule {
@WMSingleton
@Provides
static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
return new TvPipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm);
}
@WMSingleton
@Provides
static PipBoundsState providePipBoundsState(Context context) {
return new PipBoundsState(context);
static TvPipBoundsState provideTvPipBoundsState(Context context) {
return new TvPipBoundsState(context);
}
// Handler needed for loadDrawableAsync() in PipControlsViewController
@@ -111,8 +111,8 @@ public abstract class TvPipModule {
Transitions transitions, ShellTaskOrganizer shellTaskOrganizer,
PipAnimationController pipAnimationController,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipBoundsState pipBoundsState, TvPipMenuController pipMenuController) {
return new TvPipTransition(pipBoundsState, pipMenuController,
TvPipBoundsState tvPipBoundsState, TvPipMenuController pipMenuController) {
return new TvPipTransition(tvPipBoundsState, pipMenuController,
tvPipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
}
@@ -120,11 +120,11 @@ public abstract class TvPipModule {
@Provides
static TvPipMenuController providesTvPipMenuController(
Context context,
PipBoundsState pipBoundsState,
TvPipBoundsState tvPipBoundsState,
SystemWindows systemWindows,
PipMediaController pipMediaController,
@ShellMainThread Handler mainHandler) {
return new TvPipMenuController(context, pipBoundsState, systemWindows, pipMediaController,
return new TvPipMenuController(context, tvPipBoundsState, systemWindows, pipMediaController,
mainHandler);
}
@@ -155,7 +155,7 @@ public abstract class TvPipModule {
static PipTaskOrganizer providePipTaskOrganizer(Context context,
TvPipMenuController tvPipMenuController,
SyncTransactionQueue syncTransactionQueue,
PipBoundsState pipBoundsState,
TvPipBoundsState tvPipBoundsState,
PipTransitionState pipTransitionState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipAnimationController pipAnimationController,
@@ -167,7 +167,7 @@ public abstract class TvPipModule {
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context,
syncTransactionQueue, pipTransitionState, pipBoundsState, tvPipBoundsAlgorithm,
syncTransactionQueue, pipTransitionState, tvPipBoundsState, tvPipBoundsAlgorithm,
tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper,
pipTransitionController, splitScreenOptional, newSplitScreenOptional,
displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);

View File

@@ -90,6 +90,12 @@ public class PinnedStackListenerForwarder {
}
}
private void onExpandedAspectRatioChanged(float aspectRatio) {
for (PinnedTaskListener listener : mListeners) {
listener.onExpandedAspectRatioChanged(aspectRatio);
}
}
@BinderThread
private class PinnedTaskListenerImpl extends IPinnedTaskListener.Stub {
@Override
@@ -126,6 +132,15 @@ public class PinnedStackListenerForwarder {
PinnedStackListenerForwarder.this.onAspectRatioChanged(aspectRatio);
});
}
@Override
public void onExpandedAspectRatioChanged(float aspectRatio) {
mMainExecutor.execute(() -> {
PinnedStackListenerForwarder.this.onExpandedAspectRatioChanged(aspectRatio);
});
}
}
/**
@@ -142,5 +157,7 @@ public class PinnedStackListenerForwarder {
public void onActivityHidden(ComponentName componentName) {}
public void onAspectRatioChanged(float aspectRatio) {}
public void onExpandedAspectRatioChanged(float aspectRatio) {}
}
}

View File

@@ -44,7 +44,7 @@ public class PipBoundsAlgorithm {
private static final String TAG = PipBoundsAlgorithm.class.getSimpleName();
private static final float INVALID_SNAP_FRACTION = -1f;
protected final @NonNull PipBoundsState mPipBoundsState;
private final @NonNull PipBoundsState mPipBoundsState;
private final PipSnapAlgorithm mSnapAlgorithm;
private float mDefaultSizePercent;
@@ -56,7 +56,7 @@ public class PipBoundsAlgorithm {
private int mDefaultStackGravity;
private int mDefaultMinSize;
private int mOverridableMinSize;
private Point mScreenEdgeInsets;
protected Point mScreenEdgeInsets;
public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm) {
@@ -73,7 +73,7 @@ public class PipBoundsAlgorithm {
/**
* TODO: move the resources to SysUI package.
*/
private void reloadResources(Context context) {
protected void reloadResources(Context context) {
final Resources res = context.getResources();
mDefaultAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);

View File

@@ -20,9 +20,11 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.app.PictureInPictureParams;
import android.app.PictureInPictureUiState;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
@@ -46,7 +48,7 @@ import java.util.function.Consumer;
/**
* Singleton source of truth for the current state of PIP bounds.
*/
public final class PipBoundsState {
public class PipBoundsState {
public static final int STASH_TYPE_NONE = 0;
public static final int STASH_TYPE_LEFT = 1;
public static final int STASH_TYPE_RIGHT = 2;
@@ -368,11 +370,11 @@ public final class PipBoundsState {
/**
* Initialize states when first entering PiP.
*/
public void setBoundsStateForEntry(ComponentName componentName, float aspectRatio,
Size overrideMinSize) {
public void setBoundsStateForEntry(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams params, PipBoundsAlgorithm pipBoundsAlgorithm) {
setLastPipComponentName(componentName);
setAspectRatio(aspectRatio);
setOverrideMinSize(overrideMinSize);
setAspectRatio(pipBoundsAlgorithm.getAspectRatioOrDefault(params));
setOverrideMinSize(pipBoundsAlgorithm.getMinimalSize(activityInfo));
}
/** Returns whether the shelf is currently showing. */

View File

@@ -369,11 +369,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return mLeash;
}
private void setBoundsStateForEntry(ComponentName componentName, PictureInPictureParams params,
ActivityInfo activityInfo) {
mPipBoundsState.setBoundsStateForEntry(componentName,
mPipBoundsAlgorithm.getAspectRatioOrDefault(params),
mPipBoundsAlgorithm.getMinimalSize(activityInfo));
private void setBoundsStateForEntry(ComponentName componentName,
PictureInPictureParams params, ActivityInfo activityInfo) {
mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, params,
mPipBoundsAlgorithm);
}
/**

View File

@@ -187,9 +187,8 @@ public abstract class PipTransitionController implements Transitions.TransitionH
protected void setBoundsStateForEntry(ComponentName componentName,
PictureInPictureParams params,
ActivityInfo activityInfo) {
mPipBoundsState.setBoundsStateForEntry(componentName,
mPipBoundsAlgorithm.getAspectRatioOrDefault(params),
mPipBoundsAlgorithm.getMinimalSize(activityInfo));
mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, params,
mPipBoundsAlgorithm);
}
/**

View File

@@ -16,15 +16,26 @@
package com.android.wm.shell.pip.tv;
import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_HORIZONTAL;
import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_UNDETERMINED;
import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_VERTICAL;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.util.Log;
import android.util.Size;
import android.view.Gravity;
import androidx.annotation.NonNull;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
/**
@@ -33,37 +44,282 @@ import com.android.wm.shell.pip.PipSnapAlgorithm;
public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
private static final String TAG = TvPipBoundsAlgorithm.class.getSimpleName();
private static final boolean DEBUG = false;
private static final boolean DEBUG = TvPipController.DEBUG;
private final @android.annotation.NonNull TvPipBoundsState mTvPipBoundsState;
private int mFixedExpandedHeightInPx;
private int mFixedExpandedWidthInPx;
public TvPipBoundsAlgorithm(Context context,
@NonNull PipBoundsState pipBoundsState,
@NonNull TvPipBoundsState tvPipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm) {
super(context, pipBoundsState, pipSnapAlgorithm);
super(context, tvPipBoundsState, pipSnapAlgorithm);
this.mTvPipBoundsState = tvPipBoundsState;
}
@Override
protected void reloadResources(Context context) {
super.reloadResources(context);
final Resources res = context.getResources();
mFixedExpandedHeightInPx = res.getDimensionPixelSize(
com.android.internal.R.dimen.config_pictureInPictureExpandedHorizontalHeight);
mFixedExpandedWidthInPx = res.getDimensionPixelSize(
com.android.internal.R.dimen.config_pictureInPictureExpandedVerticalWidth);
}
/** Returns the destination bounds to place the PIP window on entry. */
@Override
public Rect getEntryDestinationBounds() {
if (DEBUG) Log.d(TAG, "getEntryDestinationBounds()");
if (mTvPipBoundsState.getTvExpandedAspectRatio() != 0
&& !mTvPipBoundsState.isTvPipManuallyCollapsed()) {
updatePositionOnExpandToggled(Gravity.NO_GRAVITY, true);
}
return getTvPipBounds(true);
}
/** Returns the current bounds adjusted to the new aspect ratio, if valid. */
@Override
public Rect getAdjustedDestinationBounds(Rect currentBounds, float newAspectRatio) {
if (DEBUG) Log.d(TAG, "getAdjustedDestinationBounds: " + newAspectRatio);
return getTvPipBounds(mTvPipBoundsState.isTvPipExpanded());
}
/**
* The normal bounds at a different position on the screen.
*/
public Rect getTvNormalBounds(int gravity) {
public Rect getTvNormalBounds() {
Rect normalBounds = getNormalBounds();
Rect insetBounds = new Rect();
getInsetBounds(insetBounds);
if (mPipBoundsState.isImeShowing()) {
if (DEBUG) Log.d(TAG, "IME showing, height: " + mPipBoundsState.getImeHeight());
insetBounds.bottom -= mPipBoundsState.getImeHeight();
if (mTvPipBoundsState.isImeShowing()) {
if (DEBUG) Log.d(TAG, "IME showing, height: " + mTvPipBoundsState.getImeHeight());
insetBounds.bottom -= mTvPipBoundsState.getImeHeight();
}
Rect result = new Rect();
Gravity.apply(gravity, normalBounds.width(), normalBounds.height(), insetBounds, result);
Gravity.apply(mTvPipBoundsState.getTvPipGravity(), normalBounds.width(),
normalBounds.height(), insetBounds, result);
if (DEBUG) {
Log.d(TAG, "normalBounds: " + normalBounds.toShortString());
Log.d(TAG, "insetBounds: " + insetBounds.toShortString());
Log.d(TAG, "gravity: " + Gravity.toString(gravity));
Log.d(TAG, "gravity: " + Gravity.toString(mTvPipBoundsState.getTvPipGravity()));
Log.d(TAG, "resultBounds: " + result.toShortString());
}
mTvPipBoundsState.setTvPipExpanded(false);
return result;
}
/**
* @return previous gravity if it is to be saved, or Gravity.NO_GRAVITY if not.
*/
int updatePositionOnExpandToggled(int previousGravity, boolean expanding) {
if (DEBUG) {
Log.d(TAG, "updatePositionOnExpandToggle(), expanding: " + expanding
+ ", mOrientation: " + mTvPipBoundsState.getTvFixedPipOrientation()
+ ", previous gravity: " + Gravity.toString(previousGravity));
}
if (!mTvPipBoundsState.isTvExpandedPipEnabled()) {
return Gravity.NO_GRAVITY;
}
if (expanding && mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_UNDETERMINED) {
float expandedRatio = mTvPipBoundsState.getTvExpandedAspectRatio();
if (expandedRatio == 0) {
return Gravity.NO_GRAVITY;
}
if (expandedRatio < 1) {
mTvPipBoundsState.setTvFixedPipOrientation(ORIENTATION_VERTICAL);
} else {
mTvPipBoundsState.setTvFixedPipOrientation(ORIENTATION_HORIZONTAL);
}
}
int gravityToSave = Gravity.NO_GRAVITY;
int currentGravity = mTvPipBoundsState.getTvPipGravity();
int updatedGravity;
if (expanding) {
// save collapsed gravity
gravityToSave = mTvPipBoundsState.getTvPipGravity();
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
updatedGravity =
Gravity.CENTER_HORIZONTAL | (currentGravity
& Gravity.VERTICAL_GRAVITY_MASK);
} else {
updatedGravity =
Gravity.CENTER_VERTICAL | (currentGravity
& Gravity.HORIZONTAL_GRAVITY_MASK);
}
} else {
if (previousGravity != Gravity.NO_GRAVITY) {
// The pip hasn't been moved since expanding,
// go back to previous collapsed position.
updatedGravity = previousGravity;
} else {
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
updatedGravity =
Gravity.RIGHT | (currentGravity & Gravity.VERTICAL_GRAVITY_MASK);
} else {
updatedGravity =
Gravity.BOTTOM | (currentGravity & Gravity.HORIZONTAL_GRAVITY_MASK);
}
}
}
mTvPipBoundsState.setTvPipGravity(updatedGravity);
if (DEBUG) Log.d(TAG, "new gravity: " + Gravity.toString(updatedGravity));
return gravityToSave;
}
/**
* @return true if position changed
*/
boolean updatePosition(int keycode) {
if (DEBUG) Log.d(TAG, "updatePosition, keycode: " + keycode);
// Check if position change is valid
if (mTvPipBoundsState.isTvPipExpanded()) {
int mOrientation = mTvPipBoundsState.getTvFixedPipOrientation();
if (mOrientation == ORIENTATION_VERTICAL
&& (keycode == KEYCODE_DPAD_UP || keycode == KEYCODE_DPAD_DOWN)
|| mOrientation == ORIENTATION_HORIZONTAL
&& (keycode == KEYCODE_DPAD_RIGHT || keycode == KEYCODE_DPAD_LEFT)) {
return false;
}
}
int currentGravity = mTvPipBoundsState.getTvPipGravity();
int updatedGravity;
// First axis
switch (keycode) {
case KEYCODE_DPAD_UP:
updatedGravity = Gravity.TOP;
break;
case KEYCODE_DPAD_DOWN:
updatedGravity = Gravity.BOTTOM;
break;
case KEYCODE_DPAD_LEFT:
updatedGravity = Gravity.LEFT;
break;
case KEYCODE_DPAD_RIGHT:
updatedGravity = Gravity.RIGHT;
break;
default:
updatedGravity = currentGravity;
}
// Second axis
switch (keycode) {
case KEYCODE_DPAD_UP:
case KEYCODE_DPAD_DOWN:
if (mTvPipBoundsState.isTvPipExpanded()) {
updatedGravity |= Gravity.CENTER_HORIZONTAL;
} else {
updatedGravity |= (currentGravity & Gravity.HORIZONTAL_GRAVITY_MASK);
}
break;
case KEYCODE_DPAD_LEFT:
case KEYCODE_DPAD_RIGHT:
if (mTvPipBoundsState.isTvPipExpanded()) {
updatedGravity |= Gravity.CENTER_VERTICAL;
} else {
updatedGravity |= (currentGravity & Gravity.VERTICAL_GRAVITY_MASK);
}
break;
default:
break;
}
if (updatedGravity != currentGravity) {
mTvPipBoundsState.setTvPipGravity(updatedGravity);
if (DEBUG) Log.d(TAG, "new gravity: " + Gravity.toString(updatedGravity));
return true;
}
return false;
}
/**
* Calculates the PiP bounds.
*/
public Rect getTvPipBounds(boolean expandedIfPossible) {
if (DEBUG) {
Log.d(TAG, "getExpandedBoundsIfPossible with gravity "
+ Gravity.toString(mTvPipBoundsState.getTvPipGravity())
+ ", fixed orientation: " + mTvPipBoundsState.getTvFixedPipOrientation());
}
if (!mTvPipBoundsState.isTvExpandedPipEnabled() || !expandedIfPossible) {
return getTvNormalBounds();
}
DisplayLayout displayLayout = mTvPipBoundsState.getDisplayLayout();
float expandedRatio = mTvPipBoundsState.getTvExpandedAspectRatio(); // width / height
Size expandedSize;
if (expandedRatio == 0) {
Log.d(TAG, "Expanded mode not supported");
return getTvNormalBounds();
} else if (expandedRatio < 1) {
// vertical
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y);
float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
if (maxHeight > aspectRatioHeight) {
if (DEBUG) Log.d(TAG, "Accommodate aspect ratio");
expandedSize = new Size(mFixedExpandedWidthInPx, (int) aspectRatioHeight);
} else {
if (DEBUG) Log.d(TAG, "Aspect ratio is too extreme, use max size");
expandedSize = new Size(mFixedExpandedWidthInPx, maxHeight);
}
}
} else {
// horizontal
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x);
float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
if (maxWidth > aspectRatioWidth) {
if (DEBUG) Log.d(TAG, "Accommodate aspect ratio");
expandedSize = new Size((int) aspectRatioWidth, mFixedExpandedHeightInPx);
} else {
if (DEBUG) Log.d(TAG, "Aspect ratio is too extreme, use max size");
expandedSize = new Size(maxWidth, mFixedExpandedHeightInPx);
}
}
}
if (expandedSize == null) {
return getTvNormalBounds();
}
if (DEBUG) {
Log.d(TAG, "expanded size, width: " + expandedSize.getWidth()
+ ", height: " + expandedSize.getHeight());
}
Rect insetBounds = new Rect();
getInsetBounds(insetBounds);
Rect expandedBounds = new Rect();
Gravity.apply(mTvPipBoundsState.getTvPipGravity(), expandedSize.getWidth(),
expandedSize.getHeight(), insetBounds, expandedBounds);
if (DEBUG) Log.d(TAG, "expanded bounds: " + expandedBounds.toShortString());
mTvPipBoundsState.setTvExpandedSize(expandedSize);
mTvPipBoundsState.setTvPipExpanded(true);
return expandedBounds;
}
}

View File

@@ -0,0 +1,166 @@
/*
* 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.
*/
package com.android.wm.shell.pip.tv;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.util.Size;
import android.view.Gravity;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* TV specific values of the current state of the PIP bounds.
*/
public class TvPipBoundsState extends PipBoundsState {
public static final int ORIENTATION_UNDETERMINED = 0;
public static final int ORIENTATION_VERTICAL = 1;
public static final int ORIENTATION_HORIZONTAL = 2;
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"ORIENTATION_"}, value = {
ORIENTATION_UNDETERMINED,
ORIENTATION_VERTICAL,
ORIENTATION_HORIZONTAL
})
public @interface Orientation {
}
public static final int DEFAULT_TV_GRAVITY = Gravity.BOTTOM | Gravity.RIGHT;
private boolean mIsTvExpandedPipEnabled;
private boolean mIsTvPipExpanded;
private boolean mTvPipManuallyCollapsed;
private float mTvExpandedAspectRatio;
private @Orientation int mTvFixedPipOrientation;
private int mTvPipGravity;
private @Nullable Size mTvExpandedSize;
public TvPipBoundsState(@NonNull Context context) {
super(context);
setIsTvExpandedPipEnabled(context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE));
}
/**
* Initialize states when first entering PiP.
*/
@Override
public void setBoundsStateForEntry(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams params, PipBoundsAlgorithm pipBoundsAlgorithm) {
super.setBoundsStateForEntry(componentName, activityInfo, params, pipBoundsAlgorithm);
setTvExpandedAspectRatio(params.getExpandedAspectRatio(), true);
}
/** Resets the TV PiP state for a new activity. */
public void resetTvPipState() {
mTvFixedPipOrientation = ORIENTATION_UNDETERMINED;
mTvPipGravity = DEFAULT_TV_GRAVITY;
}
/** Set the tv expanded bounds of PIP */
public void setTvExpandedSize(@Nullable Size bounds) {
mTvExpandedSize = bounds;
}
/** Get the PIP tv expanded bounds. */
@Nullable
public Size getTvExpandedSize() {
return mTvExpandedSize;
}
/** Set the PIP aspect ratio for the expanded PIP (TV) that is desired by the app. */
public void setTvExpandedAspectRatio(float aspectRatio, boolean override) {
if (override || mTvFixedPipOrientation == ORIENTATION_UNDETERMINED || aspectRatio == 0) {
mTvExpandedAspectRatio = aspectRatio;
resetTvPipState();
return;
}
if ((aspectRatio > 1 && mTvFixedPipOrientation == ORIENTATION_HORIZONTAL)
|| (aspectRatio <= 1 && mTvFixedPipOrientation == ORIENTATION_VERTICAL)) {
mTvExpandedAspectRatio = aspectRatio;
}
}
/** Get the PIP aspect ratio for the expanded PIP (TV) that is desired by the app. */
public float getTvExpandedAspectRatio() {
return mTvExpandedAspectRatio;
}
/** Sets the orientation the expanded TV PiP activity has been fixed to. */
public void setTvFixedPipOrientation(@Orientation int orientation) {
mTvFixedPipOrientation = orientation;
}
/** Returns the fixed orientation of the expanded PiP on TV. */
@Orientation
public int getTvFixedPipOrientation() {
return mTvFixedPipOrientation;
}
/** Sets the current gravity of the TV PiP. */
public void setTvPipGravity(int gravity) {
mTvPipGravity = gravity;
}
/** Returns the current gravity of the TV PiP. */
public int getTvPipGravity() {
return mTvPipGravity;
}
/** Sets whether the TV PiP is currently expanded. */
public void setTvPipExpanded(boolean expanded) {
mIsTvPipExpanded = expanded;
}
/** Returns whether the TV PiP is currently expanded. */
public boolean isTvPipExpanded() {
return mIsTvPipExpanded;
}
/** Sets whether the user has manually collapsed the TV PiP. */
public void setTvPipManuallyCollapsed(boolean collapsed) {
mTvPipManuallyCollapsed = collapsed;
}
/** Returns whether the user has manually collapsed the TV PiP. */
public boolean isTvPipManuallyCollapsed() {
return mTvPipManuallyCollapsed;
}
/** Sets whether expanded PiP is supported by the device. */
public void setIsTvExpandedPipEnabled(boolean enabled) {
mIsTvExpandedPipEnabled = enabled;
}
/** Returns whether expanded PiP is supported by the device. */
public boolean isTvExpandedPipEnabled() {
return mIsTvExpandedPipEnabled;
}
}

View File

@@ -18,10 +18,6 @@ package com.android.wm.shell.pip.tv;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import android.annotation.IntDef;
import android.app.ActivityManager;
@@ -47,7 +43,6 @@ import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
@@ -69,7 +64,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
@IntDef(prefix = { "STATE_" }, value = {
STATE_NO_PIP,
STATE_PIP,
STATE_PIP_MENU
STATE_PIP_MENU,
})
public @interface State {}
@@ -89,11 +84,9 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
*/
private static final int STATE_PIP_MENU = 2;
private static final int DEFAULT_GRAVITY = Gravity.BOTTOM | Gravity.RIGHT;
private final Context mContext;
private final PipBoundsState mPipBoundsState;
private final TvPipBoundsState mTvPipBoundsState;
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PipMediaController mPipMediaController;
@@ -103,14 +96,14 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private final TvPipImpl mImpl = new TvPipImpl();
private @State int mState = STATE_NO_PIP;
private @Gravity.GravityFlags int mGravity = DEFAULT_GRAVITY;
private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY;
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
private int mResizeAnimationDuration;
public static Pip create(
Context context,
PipBoundsState pipBoundsState,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
@@ -122,7 +115,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ShellExecutor mainExecutor) {
return new TvPipController(
context,
pipBoundsState,
tvPipBoundsState,
tvPipBoundsAlgorithm,
pipTaskOrganizer,
pipTransitionController,
@@ -136,7 +129,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private TvPipController(
Context context,
PipBoundsState pipBoundsState,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
@@ -149,9 +142,9 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mContext = context;
mMainExecutor = mainExecutor;
mPipBoundsState = pipBoundsState;
mPipBoundsState.setDisplayId(context.getDisplayId());
mPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
mTvPipBoundsState = tvPipBoundsState;
mTvPipBoundsState.setDisplayId(context.getDisplayId());
mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
mPipMediaController = pipMediaController;
@@ -225,11 +218,25 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
onPipDisappeared();
}
@Override
public void togglePipExpansion() {
if (DEBUG) Log.d(TAG, "togglePipExpansion()");
boolean expanding = !mTvPipBoundsState.isTvPipExpanded();
int saveGravity = mTvPipBoundsAlgorithm
.updatePositionOnExpandToggled(mPreviousGravity, expanding);
if (saveGravity != Gravity.NO_GRAVITY) {
mPreviousGravity = saveGravity;
}
mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
mTvPipBoundsState.setTvPipExpanded(expanding);
movePinnedStack();
}
@Override
public void movePip(int keycode) {
if (updatePosition(keycode)) {
if (DEBUG) Log.d(TAG, "New gravity: " + Gravity.toString(mGravity));
mTvPipMenuController.updateMenu(mGravity);
if (mTvPipBoundsAlgorithm.updatePosition(keycode)) {
mTvPipMenuController.updateGravity(mTvPipBoundsState.getTvPipGravity());
mPreviousGravity = Gravity.NO_GRAVITY;
movePinnedStack();
} else {
if (DEBUG) Log.d(TAG, "Position hasn't changed");
@@ -238,38 +245,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
@Override
public int getPipGravity() {
return mGravity;
return mTvPipBoundsState.getTvPipGravity();
}
/**
* @return true if position changed
*/
private boolean updatePosition(int keycode) {
if (DEBUG) Log.d(TAG, "updatePosition, keycode: " + keycode);
int updatedGravity;
switch (keycode) {
case KEYCODE_DPAD_UP:
updatedGravity = (mGravity & (~Gravity.BOTTOM)) | Gravity.TOP;
break;
case KEYCODE_DPAD_DOWN:
updatedGravity = (mGravity & (~Gravity.TOP)) | Gravity.BOTTOM;
break;
case KEYCODE_DPAD_LEFT:
updatedGravity = (mGravity & (~Gravity.RIGHT)) | Gravity.LEFT;
break;
case KEYCODE_DPAD_RIGHT:
updatedGravity = (mGravity & (~Gravity.LEFT)) | Gravity.RIGHT;
break;
default:
updatedGravity = mGravity;
}
if (updatedGravity != mGravity) {
mGravity = updatedGravity;
return true;
}
return false;
public int getOrientation() {
return mTvPipBoundsState.getTvFixedPipOrientation();
}
/**
@@ -280,11 +260,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
return;
}
Rect bounds = mTvPipBoundsAlgorithm.getTvNormalBounds(mGravity);
Rect bounds = mTvPipBoundsAlgorithm.getTvPipBounds(mTvPipBoundsState.isTvPipExpanded());
if (DEBUG) Log.d(TAG, "movePinnedStack() - new pip bounds: " + bounds.toShortString());
mPipTaskOrganizer.scheduleAnimateResizePip(bounds,
mResizeAnimationDuration, rect -> {
if (DEBUG) Log.d(TAG, "movePinnedStack() animation done");
mTvPipMenuController.updateExpansionState();
});
}
@@ -328,7 +309,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mPipNotificationController.dismiss();
mTvPipMenuController.hideMenu();
mGravity = DEFAULT_GRAVITY;
mTvPipBoundsState.resetTvPipState();
setState(STATE_NO_PIP);
mPinnedTaskId = NONEXISTENT_TASK_ID;
}
@@ -346,11 +327,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
@Override
public void onPipTransitionFinished(int direction) {
if (DEBUG) Log.d(TAG, "onPipTransition_Finished(), state=" + stateToName(mState));
if (mState == STATE_PIP_MENU) {
if (DEBUG) Log.d(TAG, " > show menu");
mTvPipMenuController.showMenu();
}
}
private void setState(@State int state) {
@@ -359,6 +335,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
+ stateToName(mState));
}
mState = state;
if (mState == STATE_PIP_MENU) {
if (DEBUG) Log.d(TAG, " > show menu");
mTvPipMenuController.showMenu();
}
}
private void loadConfigurations() {
@@ -408,19 +389,74 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
+ ", height=" + imeHeight);
}
if (imeVisible == mPipBoundsState.isImeShowing()
&& (!imeVisible || imeHeight == mPipBoundsState.getImeHeight())) {
if (imeVisible == mTvPipBoundsState.isImeShowing()
&& (!imeVisible || imeHeight == mTvPipBoundsState.getImeHeight())) {
// Nothing changed: either IME has been and remains invisible, or remains
// visible with the same height.
return;
}
mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
mTvPipBoundsState.setImeVisibility(imeVisible, imeHeight);
if (mState != STATE_NO_PIP) {
movePinnedStack();
}
}
@Override
public void onAspectRatioChanged(float ratio) {
if (DEBUG) Log.d(TAG, "onAspectRatioChanged: " + ratio);
boolean ratioChanged = mTvPipBoundsState.getAspectRatio() != ratio;
mTvPipBoundsState.setAspectRatio(ratio);
if (!mTvPipBoundsState.isTvPipExpanded() && ratioChanged) {
movePinnedStack();
}
}
@Override
public void onExpandedAspectRatioChanged(float ratio) {
if (DEBUG) Log.d(TAG, "onExpandedAspectRatioChanged: " + ratio);
// 0) No update to the ratio --> don't do anything
if (mTvPipBoundsState.getTvExpandedAspectRatio() == ratio) {
return;
}
mTvPipBoundsState.setTvExpandedAspectRatio(ratio, false);
// 1) PiP is expanded and only aspect ratio changed, but wasn't disabled
// --> update bounds, but don't toggle
if (mTvPipBoundsState.isTvPipExpanded() && ratio != 0) {
movePinnedStack();
}
// 2) PiP is expanded, but expanded PiP was disabled
// --> collapse PiP
if (mTvPipBoundsState.isTvPipExpanded() && ratio == 0) {
int saveGravity = mTvPipBoundsAlgorithm
.updatePositionOnExpandToggled(mPreviousGravity, false);
if (saveGravity != Gravity.NO_GRAVITY) {
mPreviousGravity = saveGravity;
}
mTvPipBoundsState.setTvPipExpanded(false);
movePinnedStack();
}
// 3) PiP not expanded and not manually collapsed and expand was enabled
// --> expand to new ratio
if (!mTvPipBoundsState.isTvPipExpanded() && ratio != 0
&& !mTvPipBoundsState.isTvPipManuallyCollapsed()) {
int saveGravity = mTvPipBoundsAlgorithm
.updatePositionOnExpandToggled(mPreviousGravity, true);
if (saveGravity != Gravity.NO_GRAVITY) {
mPreviousGravity = saveGravity;
}
mTvPipBoundsState.setTvPipExpanded(true);
movePinnedStack();
}
}
@Override
public void onMovementBoundsChanged(boolean fromImeAdjustment) {}

View File

@@ -65,7 +65,7 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic
setImageResource(typedArray.getResourceId(0, 0));
final int textResId = typedArray.getResourceId(1, 0);
if (textResId != 0) {
setTextAndDescription(getContext().getString(textResId));
setTextAndDescription(textResId);
}
typedArray.recycle();
}
@@ -110,6 +110,13 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic
mButtonView.setContentDescription(text);
}
/**
* Sets the text and description with the given string resource id.
*/
public void setTextAndDescription(int resId) {
setTextAndDescription(getContext().getString(resId));
}
@Override
public void setEnabled(boolean enabled) {
mButtonView.setEnabled(enabled);

View File

@@ -18,6 +18,7 @@ package com.android.wm.shell.pip.tv;
import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
import android.app.ActivityManager;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -37,7 +38,6 @@ import androidx.annotation.Nullable;
import com.android.wm.shell.R;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMenuController;
@@ -53,7 +53,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private final Context mContext;
private final SystemWindows mSystemWindows;
private final PipBoundsState mPipBoundsState;
private final TvPipBoundsState mTvPipBoundsState;
private final Handler mMainHandler;
private Delegate mDelegate;
@@ -85,11 +85,11 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
};
public TvPipMenuController(Context context, PipBoundsState pipBoundsState,
public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
SystemWindows systemWindows, PipMediaController pipMediaController,
Handler mainHandler) {
mContext = context;
mPipBoundsState = pipBoundsState;
mTvPipBoundsState = tvPipBoundsState;
mSystemWindows = systemWindows;
mMainHandler = mainHandler;
@@ -151,12 +151,13 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (DEBUG) Log.d(TAG, "showMenu()");
if (mPipMenuView != null) {
Rect menuBounds = getMenuBounds(mPipBoundsState.getBounds());
Rect menuBounds = getMenuBounds(mTvPipBoundsState.getBounds());
mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams(
MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height()));
maybeUpdateMenuViewActions();
updateExpansionState();
SurfaceControl menuSurfaceControl = mSystemWindows.getViewSurface(mPipMenuView);
SurfaceControl menuSurfaceControl = getSurfaceControl();
if (menuSurfaceControl != null) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
t.setRelativeLayer(mPipMenuView.getWindowSurfaceControl(), mLeash, 1);
@@ -167,10 +168,16 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
}
void updateMenu(int gravity) {
void updateGravity(int gravity) {
mPipMenuView.showMovementHints(gravity);
}
void updateExpansionState() {
mPipMenuView.setExpandedModeEnabled(mTvPipBoundsState.isTvExpandedPipEnabled()
&& mTvPipBoundsState.getTvExpandedAspectRatio() != 0);
mPipMenuView.setIsExpanded(mTvPipBoundsState.isTvPipExpanded());
}
private Rect getMenuBounds(Rect pipBounds) {
int extraSpaceInPx = mContext.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_outer_space);
@@ -350,7 +357,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
SurfaceControl surfaceControl = getSurfaceControl();
SyncRtSurfaceTransactionApplier.SurfaceParams params =
new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
surfaceControl).withMatrix(mMoveTransform).build();
surfaceControl)
.withMatrix(mMoveTransform)
.build();
if (pipLeash != null && transaction != null) {
SyncRtSurfaceTransactionApplier.SurfaceParams
@@ -366,6 +375,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mPipMenuView.getHandler().removeCallbacks(mUpdateEmbeddedMatrix);
mPipMenuView.getHandler().post(mUpdateEmbeddedMatrix);
}
updateMenuBounds(pipDestBounds);
}
private boolean maybeCreateSyncApplier() {
@@ -397,6 +408,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mSystemWindows.updateViewLayout(mPipMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(),
menuBounds.height()));
if (mPipMenuView != null) {
mPipMenuView.updateLayout(destinationBounds);
}
}
@Override
public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
Log.d(TAG, "onFocusTaskChanged");
}
@Override
@@ -416,6 +435,11 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mDelegate.movePipToFullscreen();
}
@Override
public void onToggleExpandedMode() {
mDelegate.togglePipExpansion();
}
interface Delegate {
void movePipToFullscreen();
@@ -423,6 +447,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
int getPipGravity();
void togglePipExpansion();
void closeMenu();
void closePip();

View File

@@ -23,19 +23,21 @@ import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import static android.view.KeyEvent.KEYCODE_ENTER;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
import android.os.IBinder;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
@@ -72,6 +74,13 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private final ImageView mArrowLeft;
private IBinder mFocusGrantToken = null;
private final ViewGroup mScrollView;
private final ViewGroup mHorizontalScrollView;
private Rect mCurrentBounds;
private final TvPipMenuActionButton mExpandButton;
public TvPipMenuView(@NonNull Context context) {
this(context, null);
}
@@ -97,6 +106,11 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
.setOnClickListener(this);
mActionButtonsContainer.findViewById(R.id.tv_pip_menu_move_button)
.setOnClickListener(this);
mExpandButton = findViewById(R.id.tv_pip_menu_expand_button);
mExpandButton.setOnClickListener(this);
mScrollView = findViewById(R.id.tv_pip_menu_scroll);
mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
@@ -106,6 +120,34 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left);
}
void updateLayout(Rect updatedBounds) {
Log.d(TAG, "update menu layout: " + updatedBounds.toShortString());
boolean previouslyVertical =
mCurrentBounds != null && mCurrentBounds.height() > mCurrentBounds.width();
boolean vertical = updatedBounds.height() > updatedBounds.width();
mCurrentBounds = updatedBounds;
if (previouslyVertical == vertical) {
if (DEBUG) Log.d(TAG, "no update for menu layout");
return;
} else {
if (DEBUG) Log.d(TAG, "change menu layout to vertical: " + vertical);
}
if (vertical) {
mHorizontalScrollView.removeView(mActionButtonsContainer);
mScrollView.addView(mActionButtonsContainer);
} else {
mScrollView.removeView(mActionButtonsContainer);
mHorizontalScrollView.addView(mActionButtonsContainer);
}
mActionButtonsContainer.setOrientation(vertical ? LinearLayout.VERTICAL
: LinearLayout.HORIZONTAL);
mScrollView.setVisibility(vertical ? VISIBLE : GONE);
mHorizontalScrollView.setVisibility(vertical ? GONE : VISIBLE);
}
void setListener(@Nullable Listener listener) {
mListener = listener;
}
@@ -114,6 +156,18 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mFocusGrantToken = token;
}
void setExpandedModeEnabled(boolean enabled) {
mExpandButton.setVisibility(enabled ? VISIBLE : GONE);
}
void setIsExpanded(boolean expanded) {
if (DEBUG) Log.d(TAG, "setIsExpanded, expanded: " + expanded);
mExpandButton.setImageResource(
expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
mExpandButton.setTextAndDescription(
expanded ? R.string.pip_collapse : R.string.pip_expand);
}
void show(boolean inMoveMode, int gravity) {
if (DEBUG) Log.d(TAG, "show(), inMoveMode: " + inMoveMode);
grantWindowFocus(true);
@@ -181,13 +235,13 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
final int actionsNumber = actions.size();
int buttonsNumber = mAdditionalButtons.size();
if (actionsNumber > buttonsNumber) {
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
// Add buttons until we have enough to display all of the actions.
while (actionsNumber > buttonsNumber) {
TvPipMenuActionButton button = new TvPipMenuActionButton(mContext);
button.setOnClickListener(this);
mActionButtonsContainer.addView(button);
mActionButtonsContainer.addView(button,
mActionButtonsContainer.getChildCount() - 1);
mAdditionalButtons.add(button);
buttonsNumber++;
@@ -239,6 +293,8 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mListener.onEnterMoveMode();
} else if (id == R.id.tv_pip_menu_close_button) {
mListener.onCloseButtonClick();
} else if (id == R.id.tv_pip_menu_expand_button) {
mListener.onToggleExpandedMode();
} else {
// This should be an "additional action"
final RemoteAction action = (RemoteAction) v.getTag();
@@ -271,6 +327,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
case KEYCODE_DPAD_RIGHT:
return mListener.onPipMovement(event.getKeyCode()) || super.dispatchKeyEvent(
event);
case KEYCODE_ENTER:
case KEYCODE_DPAD_CENTER:
return mListener.onExitMoveMode() || super.dispatchKeyEvent(event);
default:
@@ -286,10 +343,14 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
public void showMovementHints(int gravity) {
if (DEBUG) Log.d(TAG, "showMovementHints(), position: " + Gravity.toString(gravity));
animateAlphaTo((gravity & Gravity.BOTTOM) == Gravity.BOTTOM ? 1f : 0f, mArrowUp);
animateAlphaTo((gravity & Gravity.TOP) == Gravity.TOP ? 1f : 0f, mArrowDown);
animateAlphaTo((gravity & Gravity.RIGHT) == Gravity.RIGHT ? 1f : 0f, mArrowLeft);
animateAlphaTo((gravity & Gravity.LEFT) == Gravity.LEFT ? 1f : 0f, mArrowRight);
animateAlphaTo(checkGravity(gravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
animateAlphaTo(checkGravity(gravity, Gravity.TOP) ? 1f : 0f, mArrowDown);
animateAlphaTo(checkGravity(gravity, Gravity.RIGHT) ? 1f : 0f, mArrowLeft);
animateAlphaTo(checkGravity(gravity, Gravity.LEFT) ? 1f : 0f, mArrowRight);
}
private boolean checkGravity(int gravity, int feature) {
return (gravity & feature) == feature;
}
/**
@@ -330,5 +391,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
void onCloseButtonClick();
void onFullscreenButtonClick();
void onToggleExpandedMode();
}
}

View File

@@ -746,8 +746,8 @@ class ActivityClientController extends IActivityClientController.Stub {
// if it is not already expanding to fullscreen. Otherwise, the arguments will
// be used the next time the activity enters PiP.
final Task rootTask = r.getRootTask();
rootTask.setPictureInPictureAspectRatio(
r.pictureInPictureArgs.getAspectRatio());
rootTask.setPictureInPictureAspectRatio(r.pictureInPictureArgs.getAspectRatio(),
r.pictureInPictureArgs.getExpandedAspectRatio());
rootTask.setPictureInPictureActions(r.pictureInPictureArgs.getActions());
}
}
@@ -808,18 +808,28 @@ class ActivityClientController extends IActivityClientController.Stub {
+ ": Current activity does not support picture-in-picture.");
}
final float minAspectRatio = mContext.getResources().getFloat(
com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
final float maxAspectRatio = mContext.getResources().getFloat(
com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
if (params.hasSetAspectRatio()
&& !mService.mWindowManager.isValidPictureInPictureAspectRatio(
r.mDisplayContent, params.getAspectRatio())) {
final float minAspectRatio = mContext.getResources().getFloat(
com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
final float maxAspectRatio = mContext.getResources().getFloat(
com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
throw new IllegalArgumentException(String.format(caller
+ ": Aspect ratio is too extreme (must be between %f and %f).",
minAspectRatio, maxAspectRatio));
}
if (mService.mSupportsExpandedPictureInPicture && params.hasSetExpandedAspectRatio()
&& !mService.mWindowManager.isValidExpandedPictureInPictureAspectRatio(
r.mDisplayContent, params.getExpandedAspectRatio())) {
throw new IllegalArgumentException(String.format(caller
+ ": Expanded aspect ratio is not extreme enough (must not be between"
+ " %f and %f).",
minAspectRatio, maxAspectRatio));
}
// Truncate the number of actions if necessary.
params.truncateActions(ActivityTaskManager.getMaxNumPictureInPictureActions(mContext));
return r;

View File

@@ -44,6 +44,7 @@ import static android.content.pm.ApplicationInfo.FLAG_FACTORY_TEST;
import static android.content.pm.ConfigurationInfo.GL_ES_VERSION_UNDEFINED;
import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
import static android.content.pm.PackageManager.FEATURE_CANT_SAVE_STATE;
import static android.content.pm.PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
@@ -591,6 +592,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
boolean mSupportsSplitScreenMultiWindow;
boolean mSupportsFreeformWindowManagement;
boolean mSupportsPictureInPicture;
boolean mSupportsExpandedPictureInPicture;
boolean mSupportsMultiDisplay;
boolean mForceResizableActivities;
@@ -884,6 +886,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final boolean supportsMultiWindow = ActivityTaskManager.supportsMultiWindow(mContext);
final boolean supportsPictureInPicture = supportsMultiWindow &&
mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
final boolean supportsExpandedPictureInPicture =
supportsPictureInPicture && mContext.getPackageManager().hasSystemFeature(
FEATURE_EXPANDED_PICTURE_IN_PICTURE);
final boolean supportsSplitScreenMultiWindow =
ActivityTaskManager.supportsSplitScreenMultiWindow(mContext);
final boolean supportsMultiDisplay = mContext.getPackageManager()
@@ -931,12 +936,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mSupportsFreeformWindowManagement = freeformWindowManagement;
mSupportsSplitScreenMultiWindow = supportsSplitScreenMultiWindow;
mSupportsPictureInPicture = supportsPictureInPicture;
mSupportsExpandedPictureInPicture = supportsExpandedPictureInPicture;
mSupportsMultiDisplay = supportsMultiDisplay;
} else {
mSupportsMultiWindow = false;
mSupportsFreeformWindowManagement = false;
mSupportsSplitScreenMultiWindow = false;
mSupportsPictureInPicture = false;
mSupportsExpandedPictureInPicture = false;
mSupportsMultiDisplay = false;
}
mWindowManager.mRoot.onSettingsRetrieved();
@@ -3529,11 +3536,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
// Only update the saved args from the args that are set
r.setPictureInPictureParams(params);
final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
final float expandedAspectRatio = r.pictureInPictureArgs.getExpandedAspectRatio();
final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
mRootWindowContainer.moveActivityToPinnedRootTask(
r, "enterPictureInPictureMode");
final Task task = r.getTask();
task.setPictureInPictureAspectRatio(aspectRatio);
task.setPictureInPictureAspectRatio(aspectRatio, expandedAspectRatio);
task.setPictureInPictureActions(actions);
// Continue the pausing process after entering pip.

View File

@@ -93,6 +93,7 @@ class PinnedTaskController {
// The set of actions and aspect-ratio for the that are currently allowed on the PiP activity
private ArrayList<RemoteAction> mActions = new ArrayList<>();
private float mAspectRatio = -1f;
private float mExpandedAspectRatio = 0f;
// The aspect ratio bounds of the PIP.
private float mMinAspectRatio;
@@ -160,13 +161,21 @@ class PinnedTaskController {
}
/**
* @return whether the given {@param aspectRatio} is valid.
* @return whether the given {@param aspectRatio} is valid, i.e. min <= ratio <= max.
*/
public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
return Float.compare(mMinAspectRatio, aspectRatio) <= 0
&& Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
}
/**
* @return whether the given {@param aspectRatio} is valid, i.e. ratio < min or ratio > max.
*/
public boolean isValidExpandedPictureInPictureAspectRatio(float aspectRatio) {
return Float.compare(mMinAspectRatio, aspectRatio) > 0
|| Float.compare(aspectRatio, mMaxAspectRatio) > 0;
}
/**
* Called when a fullscreen task is entering PiP with display orientation change. This is used
* to avoid flickering when running PiP animation across different orientations.
@@ -377,6 +386,25 @@ class PinnedTaskController {
return mAspectRatio;
}
/**
* Sets the current aspect ratio.
*/
void setExpandedAspectRatio(float aspectRatio) {
if (Float.compare(mExpandedAspectRatio, aspectRatio) != 0) {
mExpandedAspectRatio = aspectRatio;
notifyExpandedAspectRatioChanged(aspectRatio);
notifyMovementBoundsChanged(false /* fromImeAdjustment */);
}
}
/**
* @return the current aspect ratio.
*/
float getExpandedAspectRatio() {
return mExpandedAspectRatio;
}
/**
* Sets the current set of actions.
*/
@@ -410,6 +438,15 @@ class PinnedTaskController {
}
}
private void notifyExpandedAspectRatioChanged(float aspectRatio) {
if (mPinnedTaskListener == null) return;
try {
mPinnedTaskListener.onExpandedAspectRatioChanged(aspectRatio);
} catch (RemoteException e) {
Slog.e(TAG_WM, "Error delivering aspect ratio changed event.", e);
}
}
/**
* Notifies listeners that the PIP actions have changed.
*/

View File

@@ -6013,9 +6013,9 @@ class Task extends TaskFragment {
}
/**
* Sets the current picture-in-picture aspect ratio.
* Sets the current picture-in-picture aspect ratios.
*/
void setPictureInPictureAspectRatio(float aspectRatio) {
void setPictureInPictureAspectRatio(float aspectRatio, float expandedAspectRatio) {
if (!mWmService.mAtmService.mSupportsPictureInPicture) {
return;
}
@@ -6032,16 +6032,21 @@ class Task extends TaskFragment {
final PinnedTaskController pinnedTaskController =
getDisplayContent().getPinnedTaskController();
if (Float.compare(aspectRatio, pinnedTaskController.getAspectRatio()) == 0) {
return;
}
// Notify the pinned stack controller about aspect ratio change.
// This would result a callback delivered from SystemUI to WM to start animation,
// if the bounds are ought to be altered due to aspect ratio change.
pinnedTaskController.setAspectRatio(
pinnedTaskController.isValidPictureInPictureAspectRatio(aspectRatio)
? aspectRatio : -1f);
if (Float.compare(aspectRatio, pinnedTaskController.getAspectRatio()) != 0) {
pinnedTaskController.setAspectRatio(
pinnedTaskController.isValidPictureInPictureAspectRatio(aspectRatio)
? aspectRatio : -1f);
}
if (mWmService.mAtmService.mSupportsExpandedPictureInPicture && Float.compare(
expandedAspectRatio, pinnedTaskController.getExpandedAspectRatio()) != 0) {
pinnedTaskController.setExpandedAspectRatio(pinnedTaskController
.isValidExpandedPictureInPictureAspectRatio(expandedAspectRatio)
? expandedAspectRatio : 0f);
}
}
/**

View File

@@ -2999,6 +2999,12 @@ public class WindowManagerService extends IWindowManager.Stub
aspectRatio);
}
boolean isValidExpandedPictureInPictureAspectRatio(DisplayContent displayContent,
float aspectRatio) {
return displayContent.getPinnedTaskController().isValidExpandedPictureInPictureAspectRatio(
aspectRatio);
}
@Override
public void notifyKeyguardTrustedChanged() {
synchronized (mGlobalLock) {