summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/res/drawable/home_icon.xml45
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu.xml55
-rw-r--r--libs/WindowManager/Shell/res/values-tvdpi/dimen.xml7
-rw-r--r--libs/WindowManager/Shell/res/values/colors_tv.xml3
-rw-r--r--libs/WindowManager/Shell/res/values/styles.xml9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java123
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;
}