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 0000000000000..1669d0167e4bc --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/home_icon.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml index f84b8f642953f..dbd5a9b370ab6 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"> + + + android:scrollbars="none"> + + + + + + + 5dp 1dp + + 24dp + 9sp + 14sp + 10500 + 1000 + 300 diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml index 4e7fee8e2e1e3..fa90fe36b545a 100644 --- a/libs/WindowManager/Shell/res/values/colors_tv.xml +++ b/libs/WindowManager/Shell/res/values/colors_tv.xml @@ -24,4 +24,7 @@ #990E0E0F #E8EAED #1E232C + + #99D2E3FC + #D2E3FC diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml index 7733201d2465f..19f7c3ef4364e 100644 --- a/libs/WindowManager/Shell/res/values/styles.xml +++ b/libs/WindowManager/Shell/res/values/styles.xml @@ -47,4 +47,13 @@ 96dp 48dp + + 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 0000000000000..6efdd57bdc485 --- /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 93f699e4645d4..21d5d401835d6 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 cdb18bc180beb..ea074993bae18 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 46b8e60982739..0e1f5a24f4a73 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 48f76288f1ec6..9ede4433a9783 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 = 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 } 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 3e7d10c3fcb8e..7b8dcf70cff01 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 f5c8871cb9914..5b0db8c865299 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 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; }