Merge "Add edu text to TV PiP" into tm-dev am: 49f5f057b6
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17378167 Change-Id: Icefc4bed35df71973fbebc9e17537b8d4397ca1a Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
45
libs/WindowManager/Shell/res/drawable/home_icon.xml
Normal file
45
libs/WindowManager/Shell/res/drawable/home_icon.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:gravity="center">
|
||||
<item android:gravity="center">
|
||||
<shape
|
||||
android:shape="oval">
|
||||
<stroke
|
||||
android:color="@color/tv_pip_edu_text_home_icon"
|
||||
android:width="1sp" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<size
|
||||
android:width="@dimen/pip_menu_edu_text_home_icon_outline"
|
||||
android:height="@dimen/pip_menu_edu_text_home_icon_outline"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:width="@dimen/pip_menu_edu_text_home_icon"
|
||||
android:height="@dimen/pip_menu_edu_text_home_icon"
|
||||
android:gravity="center">
|
||||
<vector
|
||||
android:width="24sp"
|
||||
android:height="24sp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/tv_pip_edu_text_home_icon"
|
||||
android:pathData="M12,3L4,9v12h5v-7h6v7h5V9z"/>
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -21,22 +21,36 @@
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center|top">
|
||||
|
||||
<View
|
||||
android:id="@+id/tv_pip"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="@dimen/pip_menu_outer_space"
|
||||
android:layout_marginStart="@dimen/pip_menu_outer_space"
|
||||
android:layout_marginEnd="@dimen/pip_menu_outer_space"/>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/tv_pip_menu_scroll"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_alignTop="@+id/tv_pip"
|
||||
android:layout_alignStart="@+id/tv_pip"
|
||||
android:layout_alignEnd="@+id/tv_pip"
|
||||
android:layout_alignBottom="@+id/tv_pip"
|
||||
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:layout_alignTop="@+id/tv_pip"
|
||||
android:layout_alignStart="@+id/tv_pip"
|
||||
android:layout_alignEnd="@+id/tv_pip"
|
||||
android:layout_alignBottom="@+id/tv_pip"
|
||||
android:gravity="center_vertical"
|
||||
android:scrollbars="none"
|
||||
android:layout_margin="@dimen/pip_menu_outer_space">
|
||||
android:scrollbars="none">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/tv_pip_menu_action_buttons"
|
||||
@@ -89,6 +103,41 @@
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
<View
|
||||
android:id="@+id/tv_pip_border"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="@dimen/pip_menu_outer_space_frame"
|
||||
android:layout_marginStart="@dimen/pip_menu_outer_space_frame"
|
||||
android:layout_marginEnd="@dimen/pip_menu_outer_space_frame"
|
||||
android:background="@drawable/tv_pip_menu_border"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/tv_pip_menu_edu_text_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@+id/tv_pip"
|
||||
android:layout_alignBottom="@+id/tv_pip_menu_frame"
|
||||
android:layout_alignStart="@+id/tv_pip"
|
||||
android:layout_alignEnd="@+id/tv_pip"
|
||||
android:background="@color/tv_pip_menu_background"
|
||||
android:clipChildren="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_pip_menu_edu_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/pip_menu_edu_text_view_height"
|
||||
android:layout_gravity="bottom|center"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="@dimen/pip_menu_border_width"
|
||||
android:text="@string/pip_edu_text"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
android:marqueeRepeatLimit="1"
|
||||
android:scrollHorizontally="true"
|
||||
android:textAppearance="@style/TvPipEduText"/>
|
||||
</FrameLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/tv_pip_menu_frame"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -37,5 +37,12 @@
|
||||
<dimen name="pip_menu_arrow_elevation">5dp</dimen>
|
||||
|
||||
<dimen name="pip_menu_elevation">1dp</dimen>
|
||||
|
||||
<dimen name="pip_menu_edu_text_view_height">24dp</dimen>
|
||||
<dimen name="pip_menu_edu_text_home_icon">9sp</dimen>
|
||||
<dimen name="pip_menu_edu_text_home_icon_outline">14sp</dimen>
|
||||
<integer name="pip_edu_text_show_duration_ms">10500</integer>
|
||||
<integer name="pip_edu_text_window_exit_animation_duration_ms">1000</integer>
|
||||
<integer name="pip_edu_text_view_exit_animation_duration_ms">300</integer>
|
||||
</resources>
|
||||
|
||||
|
||||
@@ -24,4 +24,7 @@
|
||||
<color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color>
|
||||
<color name="tv_pip_menu_focus_border">#E8EAED</color>
|
||||
<color name="tv_pip_menu_background">#1E232C</color>
|
||||
|
||||
<color name="tv_pip_edu_text">#99D2E3FC</color>
|
||||
<color name="tv_pip_edu_text_home_icon">#D2E3FC</color>
|
||||
</resources>
|
||||
|
||||
@@ -47,4 +47,13 @@
|
||||
<item name="android:layout_width">96dp</item>
|
||||
<item name="android:layout_height">48dp</item>
|
||||
</style>
|
||||
|
||||
<style name="TvPipEduText">
|
||||
<item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
|
||||
<item name="android:textAllCaps">true</item>
|
||||
<item name="android:textSize">10sp</item>
|
||||
<item name="android:lineSpacingExtra">4sp</item>
|
||||
<item name="android:lineHeight">16sp</item>
|
||||
<item name="android:textColor">@color/tv_pip_edu_text</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.style.ImageSpan;
|
||||
|
||||
/** An ImageSpan for a Drawable that is centered vertically in the line. */
|
||||
public class CenteredImageSpan extends ImageSpan {
|
||||
|
||||
private Drawable mDrawable;
|
||||
|
||||
public CenteredImageSpan(Drawable drawable) {
|
||||
super(drawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(
|
||||
Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fontMetrics) {
|
||||
final Drawable drawable = getCachedDrawable();
|
||||
final Rect rect = drawable.getBounds();
|
||||
|
||||
if (fontMetrics != null) {
|
||||
Paint.FontMetricsInt paintFontMetrics = paint.getFontMetricsInt();
|
||||
fontMetrics.ascent = paintFontMetrics.ascent;
|
||||
fontMetrics.descent = paintFontMetrics.descent;
|
||||
fontMetrics.top = paintFontMetrics.top;
|
||||
fontMetrics.bottom = paintFontMetrics.bottom;
|
||||
}
|
||||
|
||||
return rect.right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(
|
||||
Canvas canvas,
|
||||
CharSequence text,
|
||||
int start,
|
||||
int end,
|
||||
float x,
|
||||
int top,
|
||||
int y,
|
||||
int bottom,
|
||||
Paint paint) {
|
||||
final Drawable drawable = getCachedDrawable();
|
||||
canvas.save();
|
||||
final int transY = (bottom - drawable.getBounds().bottom) / 2;
|
||||
canvas.translate(x, transY);
|
||||
drawable.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
private Drawable getCachedDrawable() {
|
||||
if (mDrawable == null) {
|
||||
mDrawable = getDrawable();
|
||||
}
|
||||
return mDrawable;
|
||||
}
|
||||
}
|
||||
@@ -151,7 +151,10 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
|
||||
mKeepClearAlgorithm.setScreenSize(screenSize);
|
||||
mKeepClearAlgorithm.setMovementBounds(insetBounds);
|
||||
mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset());
|
||||
mKeepClearAlgorithm.setPipDecorInsets(mTvPipBoundsState.getPipMenuPermanentDecorInsets());
|
||||
mKeepClearAlgorithm.setPipPermanentDecorInsets(
|
||||
mTvPipBoundsState.getPipMenuPermanentDecorInsets());
|
||||
mKeepClearAlgorithm.setPipTemporaryDecorInsets(
|
||||
mTvPipBoundsState.getPipMenuTemporaryDecorInsets());
|
||||
|
||||
final Placement placement = mKeepClearAlgorithm.calculatePipPosition(
|
||||
pipSize,
|
||||
|
||||
@@ -62,6 +62,7 @@ public class TvPipBoundsState extends PipBoundsState {
|
||||
private int mTvPipGravity;
|
||||
private @Nullable Size mTvExpandedSize;
|
||||
private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE;
|
||||
private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
|
||||
|
||||
public TvPipBoundsState(@NonNull Context context) {
|
||||
super(context);
|
||||
@@ -167,4 +168,12 @@ public class TvPipBoundsState extends PipBoundsState {
|
||||
public @NonNull Insets getPipMenuPermanentDecorInsets() {
|
||||
return mPipMenuPermanentDecorInsets;
|
||||
}
|
||||
|
||||
public void setPipMenuTemporaryDecorInsets(@NonNull Insets temporaryDecorInsets) {
|
||||
mPipMenuTemporaryDecorInsets = temporaryDecorInsets;
|
||||
}
|
||||
|
||||
public @NonNull Insets getPipMenuTemporaryDecorInsets() {
|
||||
return mPipMenuTemporaryDecorInsets;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
||||
private int mPipForceCloseDelay;
|
||||
|
||||
private int mResizeAnimationDuration;
|
||||
private int mEduTextWindowExitAnimationDurationMs;
|
||||
|
||||
public static Pip create(
|
||||
Context context,
|
||||
@@ -323,11 +324,15 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePinnedStackBounds() {
|
||||
updatePinnedStackBounds(mResizeAnimationDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the PiP bounds based on the state of the PiP and keep clear areas.
|
||||
* Animates to the current PiP bounds, and schedules unstashing the PiP if necessary.
|
||||
*/
|
||||
private void updatePinnedStackBounds() {
|
||||
private void updatePinnedStackBounds(int animationDuration) {
|
||||
if (mState == STATE_NO_PIP) {
|
||||
return;
|
||||
}
|
||||
@@ -353,23 +358,26 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
||||
mUnstashRunnable = null;
|
||||
}
|
||||
if (!disallowStashing && placement.getUnstashDestinationBounds() != null) {
|
||||
mUnstashRunnable = () -> movePinnedStackTo(placement.getUnstashDestinationBounds());
|
||||
mUnstashRunnable = () -> {
|
||||
movePinnedStackTo(placement.getUnstashDestinationBounds(), animationDuration);
|
||||
};
|
||||
mMainHandler.postAtTime(mUnstashRunnable, placement.getUnstashTime());
|
||||
}
|
||||
}
|
||||
|
||||
/** Animates the PiP to the given bounds. */
|
||||
private void movePinnedStackTo(Rect bounds) {
|
||||
movePinnedStackTo(bounds, mResizeAnimationDuration);
|
||||
}
|
||||
|
||||
/** Animates the PiP to the given bounds with the given animation duration. */
|
||||
private void movePinnedStackTo(Rect bounds, int animationDuration) {
|
||||
if (DEBUG) {
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||
"%s: movePinnedStack() - new pip bounds: %s", TAG, bounds.toShortString());
|
||||
}
|
||||
mPipTaskOrganizer.scheduleAnimateResizePip(bounds,
|
||||
mResizeAnimationDuration, rect -> {
|
||||
if (DEBUG) {
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||
"%s: movePinnedStack() animation done", TAG);
|
||||
}
|
||||
animationDuration, rect -> {
|
||||
mTvPipMenuController.updateExpansionState();
|
||||
});
|
||||
}
|
||||
@@ -408,6 +416,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
||||
onPipDisappeared();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeEduText() {
|
||||
updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs);
|
||||
}
|
||||
|
||||
private void registerSessionListenerForCurrentUser() {
|
||||
mPipMediaController.registerSessionListenerForCurrentUser();
|
||||
}
|
||||
@@ -457,6 +470,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||
"%s: onPipTransition_Started(), state=%s", TAG, stateToName(mState));
|
||||
}
|
||||
mTvPipMenuController.notifyPipAnimating(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -465,6 +479,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||
"%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
|
||||
}
|
||||
mTvPipMenuController.notifyPipAnimating(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -476,6 +491,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||
"%s: onPipTransition_Finished(), state=%s", TAG, stateToName(mState));
|
||||
}
|
||||
mTvPipMenuController.notifyPipAnimating(false);
|
||||
}
|
||||
|
||||
private void setState(@State int state) {
|
||||
@@ -491,6 +507,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
||||
final Resources res = mContext.getResources();
|
||||
mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
|
||||
mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay);
|
||||
mEduTextWindowExitAnimationDurationMs =
|
||||
res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms);
|
||||
}
|
||||
|
||||
private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) {
|
||||
|
||||
@@ -95,8 +95,12 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
|
||||
private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet()
|
||||
private var lastStashTime: Long = Long.MIN_VALUE
|
||||
|
||||
/** Spaces around the PiP that we should leave space for when placing the PiP */
|
||||
private var pipDecorInsets = Insets.NONE
|
||||
/** Spaces around the PiP that we should leave space for when placing the PiP. Permanent PiP
|
||||
* decorations are relevant for calculating intersecting keep clear areas */
|
||||
private var pipPermanentDecorInsets = Insets.NONE
|
||||
/** Spaces around the PiP that we should leave space for when placing the PiP. Temporary PiP
|
||||
* decorations are not relevant for calculating intersecting keep clear areas */
|
||||
private var pipTemporaryDecorInsets = Insets.NONE
|
||||
|
||||
/**
|
||||
* Calculates the position the PiP should be placed at, taking into consideration the
|
||||
@@ -125,19 +129,21 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
|
||||
val transformedRestrictedAreas = transformAndFilterAreas(restrictedAreas)
|
||||
val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas)
|
||||
|
||||
val pipSizeWithDecors = addDecors(pipSize)
|
||||
val pipAnchorBounds = getNormalPipAnchorBounds(pipSizeWithDecors, transformedMovementBounds)
|
||||
val pipSizeWithAllDecors = addDecors(pipSize)
|
||||
val pipAnchorBoundsWithAllDecors =
|
||||
getNormalPipAnchorBounds(pipSizeWithAllDecors, transformedMovementBounds)
|
||||
|
||||
val pipAnchorBoundsWithPermanentDecors = removeTemporaryDecors(pipAnchorBoundsWithAllDecors)
|
||||
val result = calculatePipPositionTransformed(
|
||||
pipAnchorBounds,
|
||||
pipAnchorBoundsWithPermanentDecors,
|
||||
transformedRestrictedAreas,
|
||||
transformedUnrestrictedAreas
|
||||
)
|
||||
|
||||
val pipBounds = removeDecors(fromTransformedSpace(result.bounds))
|
||||
val anchorBounds = removeDecors(fromTransformedSpace(result.anchorBounds))
|
||||
val pipBounds = removePermanentDecors(fromTransformedSpace(result.bounds))
|
||||
val anchorBounds = removePermanentDecors(fromTransformedSpace(result.anchorBounds))
|
||||
val unstashedDestBounds = result.unstashDestinationBounds?.let {
|
||||
removeDecors(fromTransformedSpace(it))
|
||||
removePermanentDecors(fromTransformedSpace(it))
|
||||
}
|
||||
|
||||
return Placement(
|
||||
@@ -458,9 +464,14 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
|
||||
transformedMovementBounds = toTransformedSpace(movementBounds)
|
||||
}
|
||||
|
||||
fun setPipDecorInsets(insets: Insets) {
|
||||
if (pipDecorInsets == insets) return
|
||||
pipDecorInsets = insets
|
||||
fun setPipPermanentDecorInsets(insets: Insets) {
|
||||
if (pipPermanentDecorInsets == insets) return
|
||||
pipPermanentDecorInsets = insets
|
||||
}
|
||||
|
||||
fun setPipTemporaryDecorInsets(insets: Insets) {
|
||||
if (pipTemporaryDecorInsets == insets) return
|
||||
pipTemporaryDecorInsets = insets
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -756,16 +767,26 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
|
||||
*/
|
||||
private fun addDecors(size: Size): Size {
|
||||
val bounds = Rect(0, 0, size.width, size.height)
|
||||
bounds.inset(pipDecorInsets)
|
||||
bounds.inset(pipPermanentDecorInsets)
|
||||
bounds.inset(pipTemporaryDecorInsets)
|
||||
|
||||
return Size(bounds.width(), bounds.height())
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the space that was reserved for decorations around the pip
|
||||
* Removes the space that was reserved for permanent decorations around the pip
|
||||
*/
|
||||
private fun removeDecors(bounds: Rect): Rect {
|
||||
val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipDecorInsets)
|
||||
private fun removePermanentDecors(bounds: Rect): Rect {
|
||||
val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipPermanentDecorInsets)
|
||||
bounds.inset(pipDecorReverseInsets)
|
||||
return bounds
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the space that was reserved for temporary decorations around the pip
|
||||
*/
|
||||
private fun removeTemporaryDecors(bounds: Rect): Rect {
|
||||
val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipTemporaryDecorInsets)
|
||||
bounds.inset(pipDecorReverseInsets)
|
||||
return bounds
|
||||
}
|
||||
|
||||
@@ -63,6 +63,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
||||
private final SystemWindows mSystemWindows;
|
||||
private final TvPipBoundsState mTvPipBoundsState;
|
||||
private final Handler mMainHandler;
|
||||
private final int mPipMenuBorderWidth;
|
||||
private final int mPipEduTextShowDurationMs;
|
||||
private final int mPipEduTextHeight;
|
||||
|
||||
private Delegate mDelegate;
|
||||
private SurfaceControl mLeash;
|
||||
@@ -98,7 +101,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
||||
if (DEBUG) e.printStackTrace();
|
||||
}
|
||||
};
|
||||
private final int mPipMenuSurfaceOuterSpace;
|
||||
private final Runnable mCloseEduTextRunnable = this::closeEduText;
|
||||
|
||||
public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
|
||||
SystemWindows systemWindows, PipMediaController pipMediaController,
|
||||
@@ -122,13 +125,12 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
||||
|
||||
pipMediaController.addActionListener(this::onMediaActionsChanged);
|
||||
|
||||
mPipMenuSurfaceOuterSpace = context.getResources()
|
||||
.getDimensionPixelSize(R.dimen.pip_menu_outer_space);
|
||||
|
||||
final int pipBorderWidth = context.getResources()
|
||||
mPipEduTextShowDurationMs = context.getResources()
|
||||
.getInteger(R.integer.pip_edu_text_show_duration_ms);
|
||||
mPipEduTextHeight = context.getResources()
|
||||
.getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
|
||||
mPipMenuBorderWidth = context.getResources()
|
||||
.getDimensionPixelSize(R.dimen.pip_menu_border_width);
|
||||
mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-pipBorderWidth,
|
||||
-pipBorderWidth, -pipBorderWidth, -pipBorderWidth));
|
||||
}
|
||||
|
||||
void setDelegate(Delegate delegate) {
|
||||
@@ -169,6 +171,11 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
||||
|
||||
attachPipBackgroundView();
|
||||
attachPipMenuView();
|
||||
|
||||
mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-mPipMenuBorderWidth,
|
||||
-mPipMenuBorderWidth, -mPipMenuBorderWidth, -mPipMenuBorderWidth));
|
||||
mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -mPipEduTextHeight));
|
||||
mMainHandler.postDelayed(mCloseEduTextRunnable, mPipEduTextShowDurationMs);
|
||||
}
|
||||
|
||||
private void attachPipMenuView() {
|
||||
@@ -204,6 +211,10 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
||||
0 /* displayId */, SHELL_ROOT_LAYER_PIP);
|
||||
}
|
||||
|
||||
void notifyPipAnimating(boolean animating) {
|
||||
mPipMenuView.setEduTextActive(!animating);
|
||||
}
|
||||
|
||||
void showMovementMenuOnly() {
|
||||
if (DEBUG) {
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||
@@ -228,6 +239,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
||||
if (mPipMenuView == null) {
|
||||
return;
|
||||
}
|
||||
maybeCloseEduText();
|
||||
maybeUpdateMenuViewActions();
|
||||
updateExpansionState();
|
||||
|
||||
@@ -239,6 +251,19 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeCloseEduText() {
|
||||
if (mMainHandler.hasCallbacks(mCloseEduTextRunnable)) {
|
||||
mMainHandler.removeCallbacks(mCloseEduTextRunnable);
|
||||
mCloseEduTextRunnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
private void closeEduText() {
|
||||
mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
|
||||
mPipMenuView.hideEduText();
|
||||
mDelegate.closeEduText();
|
||||
}
|
||||
|
||||
void updateGravity(int gravity) {
|
||||
mPipMenuView.showMovementHints(gravity);
|
||||
}
|
||||
@@ -250,9 +275,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
||||
}
|
||||
|
||||
private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
|
||||
Rect menuBounds = new Rect(pipBounds);
|
||||
menuBounds.inset(-mPipMenuSurfaceOuterSpace, -mPipMenuSurfaceOuterSpace);
|
||||
return menuBounds;
|
||||
return mPipMenuView.getPipMenuContainerBounds(pipBounds);
|
||||
}
|
||||
|
||||
void closeMenu() {
|
||||
@@ -321,6 +344,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
||||
@Override
|
||||
public void detach() {
|
||||
closeMenu();
|
||||
mMainHandler.removeCallbacks(mCloseEduTextRunnable);
|
||||
detachPipMenu();
|
||||
mLeash = null;
|
||||
}
|
||||
@@ -598,6 +622,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
||||
|
||||
void onMenuClosed();
|
||||
|
||||
void closeEduText();
|
||||
|
||||
void closePip();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,11 +25,17 @@ 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.animation.ValueAnimator;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.RemoteAction;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.text.Annotation;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannedString;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
@@ -40,6 +46,7 @@ import android.view.ViewRootImpl;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -49,6 +56,7 @@ import com.android.wm.shell.R;
|
||||
import com.android.wm.shell.protolog.ShellProtoLogGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -67,6 +75,15 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
|
||||
private final LinearLayout mActionButtonsContainer;
|
||||
private final View mMenuFrameView;
|
||||
private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
|
||||
private final View mPipFrameView;
|
||||
private final View mPipView;
|
||||
private final TextView mEduTextView;
|
||||
private final View mEduTextContainerView;
|
||||
private final int mPipMenuOuterSpace;
|
||||
private final int mPipMenuBorderWidth;
|
||||
private final int mEduTextFadeExitAnimationDurationMs;
|
||||
private final int mEduTextSlideExitAnimationDurationMs;
|
||||
private int mEduTextHeight;
|
||||
|
||||
private final ImageView mArrowUp;
|
||||
private final ImageView mArrowRight;
|
||||
@@ -76,7 +93,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
|
||||
private final ViewGroup mScrollView;
|
||||
private final ViewGroup mHorizontalScrollView;
|
||||
|
||||
private Rect mCurrentBounds;
|
||||
private Rect mCurrentPipBounds;
|
||||
|
||||
private final TvPipMenuActionButton mExpandButton;
|
||||
private final TvPipMenuActionButton mCloseButton;
|
||||
@@ -118,24 +135,86 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
|
||||
mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
|
||||
|
||||
mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
|
||||
mPipFrameView = findViewById(R.id.tv_pip_border);
|
||||
mPipView = findViewById(R.id.tv_pip);
|
||||
|
||||
mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up);
|
||||
mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
|
||||
mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
|
||||
mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left);
|
||||
|
||||
mEduTextView = findViewById(R.id.tv_pip_menu_edu_text);
|
||||
mEduTextContainerView = findViewById(R.id.tv_pip_menu_edu_text_container);
|
||||
|
||||
mPipMenuFadeAnimationDuration = context.getResources()
|
||||
.getInteger(R.integer.pip_menu_fade_animation_duration);
|
||||
mPipMenuOuterSpace = context.getResources()
|
||||
.getDimensionPixelSize(R.dimen.pip_menu_outer_space);
|
||||
mPipMenuBorderWidth = context.getResources()
|
||||
.getDimensionPixelSize(R.dimen.pip_menu_border_width);
|
||||
mEduTextHeight = context.getResources()
|
||||
.getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
|
||||
mEduTextFadeExitAnimationDurationMs = context.getResources()
|
||||
.getInteger(R.integer.pip_edu_text_view_exit_animation_duration_ms);
|
||||
mEduTextSlideExitAnimationDurationMs = context.getResources()
|
||||
.getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms);
|
||||
|
||||
initEduText();
|
||||
}
|
||||
|
||||
void updateLayout(Rect updatedBounds) {
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||
"%s: update menu layout: %s", TAG, updatedBounds.toShortString());
|
||||
boolean previouslyVertical =
|
||||
mCurrentBounds != null && mCurrentBounds.height() > mCurrentBounds.width();
|
||||
boolean vertical = updatedBounds.height() > updatedBounds.width();
|
||||
void initEduText() {
|
||||
final SpannedString eduText = (SpannedString) getResources().getText(R.string.pip_edu_text);
|
||||
final SpannableString spannableString = new SpannableString(eduText);
|
||||
Arrays.stream(eduText.getSpans(0, eduText.length(), Annotation.class)).findFirst()
|
||||
.ifPresent(annotation -> {
|
||||
final Drawable icon =
|
||||
getResources().getDrawable(R.drawable.home_icon, mContext.getTheme());
|
||||
if (icon != null) {
|
||||
icon.mutate();
|
||||
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
|
||||
spannableString.setSpan(new CenteredImageSpan(icon),
|
||||
eduText.getSpanStart(annotation),
|
||||
eduText.getSpanEnd(annotation),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
});
|
||||
|
||||
mEduTextView.setText(spannableString);
|
||||
}
|
||||
|
||||
void setEduTextActive(boolean active) {
|
||||
mEduTextView.setSelected(active);
|
||||
}
|
||||
|
||||
void hideEduText() {
|
||||
final ValueAnimator heightAnimation = ValueAnimator.ofInt(mEduTextHeight, 0);
|
||||
heightAnimation.setDuration(mEduTextSlideExitAnimationDurationMs);
|
||||
heightAnimation.setInterpolator(TvPipInterpolators.BROWSE);
|
||||
heightAnimation.addUpdateListener(animator -> {
|
||||
mEduTextHeight = (int) animator.getAnimatedValue();
|
||||
});
|
||||
mEduTextView.animate()
|
||||
.alpha(0f)
|
||||
.setInterpolator(TvPipInterpolators.EXIT)
|
||||
.setDuration(mEduTextFadeExitAnimationDurationMs)
|
||||
.withEndAction(() -> {
|
||||
mEduTextContainerView.setVisibility(GONE);
|
||||
}).start();
|
||||
heightAnimation.start();
|
||||
}
|
||||
|
||||
void updateLayout(Rect updatedPipBounds) {
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||
"%s: update menu layout: %s", TAG, updatedPipBounds.toShortString());
|
||||
|
||||
boolean previouslyVertical =
|
||||
mCurrentPipBounds != null && mCurrentPipBounds.height() > mCurrentPipBounds.width();
|
||||
boolean vertical = updatedPipBounds.height() > updatedPipBounds.width();
|
||||
|
||||
mCurrentPipBounds = updatedPipBounds;
|
||||
|
||||
updatePipFrameBounds();
|
||||
|
||||
mCurrentBounds = updatedBounds;
|
||||
if (previouslyVertical == vertical) {
|
||||
if (DEBUG) {
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||
@@ -163,6 +242,38 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
|
||||
mHorizontalScrollView.setVisibility(vertical ? GONE : VISIBLE);
|
||||
}
|
||||
|
||||
Rect getPipMenuContainerBounds(Rect pipBounds) {
|
||||
final Rect menuUiBounds = new Rect(pipBounds);
|
||||
menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
|
||||
menuUiBounds.bottom += mEduTextHeight;
|
||||
return menuUiBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update mPipFrameView's bounds according to the new pip window bounds. We can't
|
||||
* make mPipFrameView match_parent, because the pip menu might contain other content around
|
||||
* the pip window (e.g. edu text).
|
||||
* TvPipMenuView needs to account for this so that it can draw a white border around the whole
|
||||
* pip menu when it gains focus.
|
||||
*/
|
||||
private void updatePipFrameBounds() {
|
||||
final ViewGroup.LayoutParams pipFrameParams = mPipFrameView.getLayoutParams();
|
||||
if (pipFrameParams != null) {
|
||||
pipFrameParams.width = mCurrentPipBounds.width() + 2 * mPipMenuBorderWidth;
|
||||
pipFrameParams.height = mCurrentPipBounds.height() + 2 * mPipMenuBorderWidth;
|
||||
mPipFrameView.setLayoutParams(pipFrameParams);
|
||||
}
|
||||
|
||||
final ViewGroup.LayoutParams pipViewParams = mPipView.getLayoutParams();
|
||||
if (pipViewParams != null) {
|
||||
pipViewParams.width = mCurrentPipBounds.width();
|
||||
pipViewParams.height = mCurrentPipBounds.height();
|
||||
mPipView.setLayoutParams(pipViewParams);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void setListener(@Nullable Listener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user