diff options
12 files changed, 419 insertions, 42 deletions
diff --git a/libs/WindowManager/Shell/res/drawable/home_icon.xml b/libs/WindowManager/Shell/res/drawable/home_icon.xml new file mode 100644 index 000000000000..1669d0167e4b --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/home_icon.xml @@ -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> diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml index f84b8f642953..dbd5a9b370ab 100644 --- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml +++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml @@ -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" @@ -90,6 +104,41 @@ </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" android:layout_height="match_parent" diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml index 63c26dadfab4..776b18ecc01b 100644 --- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml +++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml @@ -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> diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml index 4e7fee8e2e1e..fa90fe36b545 100644 --- a/libs/WindowManager/Shell/res/values/colors_tv.xml +++ b/libs/WindowManager/Shell/res/values/colors_tv.xml @@ -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> diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml index 7733201d2465..19f7c3ef4364 100644 --- a/libs/WindowManager/Shell/res/values/styles.xml +++ b/libs/WindowManager/Shell/res/values/styles.xml @@ -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> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java new file mode 100644 index 000000000000..6efdd57bdc48 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java @@ -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; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java index 93f699e4645d..21d5d401835d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java @@ -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, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java index cdb18bc180be..ea074993bae1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java @@ -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; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 46b8e6098273..0e1f5a24f4a7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -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) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt index 48f76288f1ec..9ede4433a978 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt @@ -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 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 removeDecors(bounds: Rect): Rect { - val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipDecorInsets) + private fun removeTemporaryDecors(bounds: Rect): Rect { + val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipTemporaryDecorInsets) bounds.inset(pipDecorReverseInsets) return bounds } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index 3e7d10c3fcb8..7b8dcf70cff0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -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(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java index f5c8871cb991..5b0db8c86529 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java @@ -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) { + 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, updatedBounds.toShortString()); + "%s: update menu layout: %s", TAG, updatedPipBounds.toShortString()); + boolean previouslyVertical = - mCurrentBounds != null && mCurrentBounds.height() > mCurrentBounds.width(); - boolean vertical = updatedBounds.height() > updatedBounds.width(); + 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; } |