From a7f69740b5b4d74cd0736b05220d2c8633b07a63 Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Fri, 3 Feb 2017 11:00:20 -0800 Subject: [PATCH] Update how PIP size is determined - minSize = 108dp - defaultSmallestEdge = max(23% of screen width, minSize) - the shortest edge of the PIP should be minSize and the rest scales according to the aspect ratio - rather than a default PIP size, use default aspect ratio - adding expand button - fitting actions to spec Fixes: 35358504 Test: manually used test app to try different aspect ratios Change-Id: Ib6890fb7824889b9edeea7efb5b9771e64fc1514 Signed-off-by: Winson Chung --- .../internal/policy/PipSnapAlgorithm.java | 48 ++++++++++++++++++- core/res/res/values-television/config.xml | 7 +-- core/res/res/values/config.xml | 14 ++++-- core/res/res/values/dimens.xml | 3 ++ core/res/res/values/symbols.xml | 5 +- packages/SystemUI/res/drawable/pip_expand.xml | 38 +++++++++++++++ .../SystemUI/res/layout/pip_menu_activity.xml | 21 ++++++-- packages/SystemUI/res/values/dimens.xml | 7 +++ .../systemui/pip/phone/PipMenuActivity.java | 41 ++++++++++++---- .../pip/phone/PipMenuActivityController.java | 15 ++++++ .../systemui/pip/phone/PipMotionHelper.java | 4 +- .../systemui/pip/phone/PipTouchHandler.java | 22 ++++++++- .../server/wm/PinnedStackController.java | 39 +++++++++------ 13 files changed, 225 insertions(+), 39 deletions(-) create mode 100644 packages/SystemUI/res/drawable/pip_expand.xml diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java index 040f150e8c959..f88ac495fe529 100644 --- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java +++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java @@ -18,9 +18,11 @@ package com.android.internal.policy; import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; +import android.util.Size; import android.view.Gravity; import android.view.ViewConfiguration; import android.widget.Scroller; @@ -56,6 +58,10 @@ public class PipSnapAlgorithm { private final int mDefaultSnapMode = SNAP_MODE_CORNERS_AND_EDGES; private int mSnapMode = mDefaultSnapMode; + private final float mDefaultSizePercent; + private final float mMinAspectRatioForMinSize; + private final float mMaxAspectRatioForMinSize; + private Scroller mScroller; private int mOrientation = Configuration.ORIENTATION_UNDEFINED; @@ -63,9 +69,15 @@ public class PipSnapAlgorithm { private boolean mIsMinimized; public PipSnapAlgorithm(Context context) { + Resources res = context.getResources(); mContext = context; - mMinimizedVisibleSize = context.getResources().getDimensionPixelSize( + mMinimizedVisibleSize = res.getDimensionPixelSize( com.android.internal.R.dimen.pip_minimized_visible_size); + mDefaultSizePercent = res.getFloat( + com.android.internal.R.dimen.config_pictureInPictureDefaultSizePercent); + mMaxAspectRatioForMinSize = res.getFloat( + com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize); + mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize; onConfigurationChanged(); } @@ -241,6 +253,40 @@ public class PipSnapAlgorithm { movementBoundsOut.bottom -= imeHeight; } + /** + * @return the size of the PiP at the given {@param aspectRatio}, ensuring that the minimum edge + * is at least {@param minEdgeSize}. + */ + public Size getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth, + int displayHeight) { + final int smallestDisplaySize = Math.min(displayWidth, displayHeight); + final int minSize = (int) Math.max(minEdgeSize, smallestDisplaySize * mDefaultSizePercent); + + final int width; + final int height; + if (aspectRatio <= mMinAspectRatioForMinSize || aspectRatio > mMaxAspectRatioForMinSize) { + // Beyond these points, we can just use the min size as the shorter edge + if (aspectRatio <= 1) { + // Portrait, width is the minimum size + width = minSize; + height = Math.round(width / aspectRatio); + } else { + // Landscape, height is the minimum size + height = minSize; + width = Math.round(height * aspectRatio); + } + } else { + // Within these points, we ensure that the bounds fit within the radius of the limits + // at the points + final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize; + final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize); + height = (int) Math.round(Math.sqrt((radius * radius) / + (aspectRatio * aspectRatio + 1))); + width = Math.round(height * aspectRatio); + } + return new Size(width, height); + } + /** * @return the closest point in {@param points} to the given {@param x} and {@param y}. */ diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml index c27cb06deb8d3..1987ac454d86c 100644 --- a/core/res/res/values-television/config.xml +++ b/core/res/res/values-television/config.xml @@ -24,9 +24,10 @@ false - - 240x135 + + 0.14 diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ab2e090b28691..a3a0c830a0cfb 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2545,9 +2545,17 @@ These values are in DPs and will be converted to pixel sizes internally. --> 16x16 - - 192x120 + + 0.23 + + + 1.777778 + + + 1.777778 diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 0ab17842ebb7c..d0127a3a6cd6a 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -486,6 +486,9 @@ 220dp + + 108dp + 80dp diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 3ba6a274ee8a3..31dcfc6d59221 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -318,7 +318,9 @@ - + + + @@ -1777,6 +1779,7 @@ + diff --git a/packages/SystemUI/res/drawable/pip_expand.xml b/packages/SystemUI/res/drawable/pip_expand.xml new file mode 100644 index 0000000000000..e34a95d257ac8 --- /dev/null +++ b/packages/SystemUI/res/drawable/pip_expand.xml @@ -0,0 +1,38 @@ + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml index 0f5ca9bdf2fc5..f38c8ff5da0ce 100644 --- a/packages/SystemUI/res/layout/pip_menu_activity.xml +++ b/packages/SystemUI/res/layout/pip_menu_activity.xml @@ -31,20 +31,33 @@ android:src="@drawable/ic_close_white" android:background="?android:selectableItemBackgroundBorderless" /> + + + + + android:orientation="horizontal" + android:divider="@android:color/transparent" + android:showDividers="middle" /> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 3512761afbd2d..f331d8782a2d3 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -731,6 +731,13 @@ 48dp + + 160dp + + + 8dp + 18dp 18dp diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index f67241ef178ad..65de22e21775b 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -40,8 +40,9 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.WindowManager.LayoutParams; +import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.TextView; +import android.widget.LinearLayout; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -57,8 +58,9 @@ public class PipMenuActivity extends Activity { private static final String TAG = "PipMenuActivity"; public static final int MESSAGE_SHOW_MENU = 1; - public static final int MESSAGE_HIDE_MENU = 2; - public static final int MESSAGE_UPDATE_ACTIONS = 3; + public static final int MESSAGE_POKE_MENU = 2; + public static final int MESSAGE_HIDE_MENU = 3; + public static final int MESSAGE_UPDATE_ACTIONS = 4; private static final long INITIAL_DISMISS_DELAY = 2000; private static final long POST_INTERACTION_DISMISS_DELAY = 1500; @@ -67,7 +69,9 @@ public class PipMenuActivity extends Activity { private boolean mMenuVisible; private final List mActions = new ArrayList<>(); private View mMenuContainer; + private LinearLayout mActionsGroup; private View mDismissButton; + private int mBetweenActionPaddingLand; private ObjectAnimator mMenuContainerAnimator; @@ -83,6 +87,9 @@ public class PipMenuActivity extends Activity { case MESSAGE_SHOW_MENU: showMenu(); break; + case MESSAGE_POKE_MENU: + cancelDelayedFinish(); + break; case MESSAGE_HIDE_MENU: hideMenu(); break; @@ -127,6 +134,9 @@ public class PipMenuActivity extends Activity { mDismissButton.setOnClickListener((v) -> { dismissPip(); }); + mActionsGroup = (LinearLayout) findViewById(R.id.actions_group); + mBetweenActionPaddingLand = getResources().getDimensionPixelSize( + R.dimen.pip_between_action_padding_land); notifyActivityCallback(mMessenger); showMenu(); @@ -260,26 +270,30 @@ public class PipMenuActivity extends Activity { } private void updateActionViews() { + ViewGroup expandContainer = (ViewGroup) findViewById(R.id.expand_container); ViewGroup actionsContainer = (ViewGroup) findViewById(R.id.actions_container); actionsContainer.setOnTouchListener((v, ev) -> { // Do nothing, prevent click through to parent return true; }); + int actionsContainerHeight = 0; if (mActions.isEmpty()) { actionsContainer.setVisibility(View.INVISIBLE); } else { actionsContainer.setVisibility(View.VISIBLE); - ViewGroup actionsGroup = (ViewGroup) findViewById(R.id.actions); - if (actionsGroup != null) { - actionsGroup.removeAllViews(); + if (mActionsGroup != null) { + mActionsGroup.removeAllViews(); // Recreate the layout + final View decorView = getWindow().getDecorView(); + final boolean isLandscapePip = decorView.getMeasuredWidth() + > decorView.getMeasuredHeight(); final LayoutInflater inflater = LayoutInflater.from(this); for (int i = 0; i < mActions.size(); i++) { final RemoteAction action = mActions.get(i); final ImageView actionView = (ImageView) inflater.inflate( - R.layout.pip_menu_action, actionsGroup, false); + R.layout.pip_menu_action, mActionsGroup, false); action.getIcon().loadDrawableAsync(this, d -> { d.setTint(Color.WHITE); actionView.setImageDrawable(d); @@ -292,10 +306,21 @@ public class PipMenuActivity extends Activity { Log.w(TAG, "Failed to send action", e); } }); - actionsGroup.addView(actionView); + if (isLandscapePip && i > 0) { + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) + actionView.getLayoutParams(); + lp.leftMargin = mBetweenActionPaddingLand; + } + mActionsGroup.addView(actionView); } } + actionsContainerHeight = actionsContainer.getLayoutParams().height; } + + // Update the expand container margin to account for the existence of the action container + ((FrameLayout.LayoutParams) expandContainer.getLayoutParams()).bottomMargin = + actionsContainerHeight; + expandContainer.requestLayout(); } private void notifyRegisterInputConsumer() { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index 76c3caf9f454e..0b1c3ecc47bd1 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -209,6 +209,21 @@ public class PipMenuActivityController { } } + /** + * Pokes the menu, indicating that the user is interacting with it. + */ + public void pokeMenu() { + if (mToActivityMessenger != null) { + Message m = Message.obtain(); + m.what = PipMenuActivity.MESSAGE_POKE_MENU; + try { + mToActivityMessenger.send(m); + } catch (RemoteException e) { + Log.e(TAG, "Could not notify poke menu", e); + } + } + } + /** * Hides the menu activity. */ diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index a26fd4aafd1f2..a9d7457dc09e0 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -57,8 +57,8 @@ public class PipMotionHelper { private static final int DEFAULT_MOVE_STACK_DURATION = 225; private static final int SNAP_STACK_DURATION = 225; private static final int DISMISS_STACK_DURATION = 375; - private static final int SHRINK_STACK_FROM_MENU_DURATION = 175; - private static final int EXPAND_STACK_TO_MENU_DURATION = 175; + private static final int SHRINK_STACK_FROM_MENU_DURATION = 150; + private static final int EXPAND_STACK_TO_MENU_DURATION = 150; private static final int EXPAND_STACK_TO_FULLSCREEN_DURATION = 225; private static final int MINIMIZE_STACK_MAX_DURATION = 200; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 54b15aa17e691..ebe6d2572dffd 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -18,11 +18,13 @@ package com.android.systemui.pip.phone; import android.app.IActivityManager; import android.content.Context; +import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; import android.util.Log; +import android.util.Size; import android.view.IPinnedStackController; import android.view.IWindowManager; import android.view.MotionEvent; @@ -31,6 +33,7 @@ import android.view.ViewConfiguration; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.policy.PipSnapAlgorithm; +import com.android.systemui.R; import com.android.systemui.statusbar.FlingAnimationUtils; import java.io.PrintWriter; @@ -69,6 +72,7 @@ public class PipTouchHandler { private Rect mNormalMovementBounds = new Rect(); private Rect mExpandedBounds = new Rect(); private Rect mExpandedMovementBounds = new Rect(); + private int mExpandedShortestEdgeSize; private Handler mHandler = new Handler(); private Runnable mShowDismissAffordance = new Runnable() { @@ -145,6 +149,8 @@ public class PipTouchHandler { }; mMotionHelper = new PipMotionHelper(mContext, mActivityManager, mSnapAlgorithm, mFlingAnimationUtils); + mExpandedShortestEdgeSize = context.getResources().getDimensionPixelSize( + R.dimen.pip_expanded_shortest_edge_size); // Register the listener for input consumer touch events inputConsumerController.setTouchListener(this::handleTouchEvent); @@ -181,8 +187,14 @@ public class PipTouchHandler { Rect normalMovementBounds = new Rect(); mSnapAlgorithm.getMovementBounds(mNormalBounds, insetBounds, normalMovementBounds, mIsImeShowing ? mImeHeight : 0); - // TODO: Figure out the expanded size policy - mExpandedBounds = new Rect(normalBounds); + + // Calculate the expanded size + float aspectRatio = (float) normalBounds.width() / normalBounds.height(); + Point displaySize = new Point(); + mContext.getDisplay().getRealSize(displaySize); + Size expandedSize = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, + mExpandedShortestEdgeSize, displaySize.x, displaySize.y); + mExpandedBounds.set(0, 0, expandedSize.getWidth(), expandedSize.getHeight()); Rect expandedMovementBounds = new Rect(); mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds, mIsImeShowing ? mImeHeight : 0); @@ -360,6 +372,12 @@ public class PipTouchHandler { return; } + // If the menu is still visible, and we aren't minimized, then just poke the menu + // so that it will timeout after the user stops touching it + if (mMenuController.isMenuVisible() && !mIsMinimized) { + mMenuController.pokeMenu(); + } + if (ENABLE_DRAG_TO_DISMISS) { mDismissViewController.createDismissTarget(); mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY); diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java index 2a20a70b6ee9e..9558068972cbc 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedStackController.java @@ -94,13 +94,16 @@ class PinnedStackController { // The size and position information that describes where the pinned stack will go by default. private int mDefaultStackGravity; - private Size mDefaultStackSize; + private float mDefaultAspectRatio; private Point mScreenEdgeInsets; // The aspect ratio bounds of the PIP. private float mMinAspectRatio; private float mMaxAspectRatio; + // The minimum edge size of the normal PiP bounds. + private int mMinSize; + // Temp vars for calculation private final DisplayMetrics mTmpMetrics = new DisplayMetrics(); private final Rect mTmpInsets = new Rect(); @@ -151,15 +154,15 @@ class PinnedStackController { */ void reloadResources() { final Resources res = mService.mContext.getResources(); - final Size defaultSizeDp = Size.parseSize(res.getString( - com.android.internal.R.string.config_defaultPictureInPictureSize)); + mMinSize = res.getDimensionPixelSize( + com.android.internal.R.dimen.default_minimal_size_pip_resizable_task); + mDefaultAspectRatio = res.getFloat( + com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio); final Size screenEdgeInsetsDp = Size.parseSize(res.getString( com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets)); mDefaultStackGravity = res.getInteger( com.android.internal.R.integer.config_defaultPictureInPictureGravity); mDisplayContent.getDisplay().getRealMetrics(mTmpMetrics); - mDefaultStackSize = new Size(dpToPx(defaultSizeDp.getWidth(), mTmpMetrics), - dpToPx(defaultSizeDp.getHeight(), mTmpMetrics)); mScreenEdgeInsets = new Point(dpToPx(screenEdgeInsetsDp.getWidth(), mTmpMetrics), dpToPx(screenEdgeInsetsDp.getHeight(), mTmpMetrics)); mMinAspectRatio = res.getFloat( @@ -199,16 +202,13 @@ class PinnedStackController { * specified aspect ratio. */ Rect transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio) { - // Save the snap fraction, calculate the aspect ratio based on the current bounds + // Save the snap fraction, calculate the aspect ratio based on screen size final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds, getMovementBounds(stackBounds)); - final float radius = PointF.length(stackBounds.width(), stackBounds.height()); - final int height = (int) Math.round(Math.sqrt((radius * radius) / - (aspectRatio * aspectRatio + 1))); - final int width = Math.round(height * aspectRatio); - final int left = (int) (stackBounds.centerX() - width / 2f); - final int top = (int) (stackBounds.centerY() - height / 2f); - stackBounds.set(left, top, left + width, top + height); + final Size size = getSize(aspectRatio); + final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f); + final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f); + stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight()); mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction); if (mIsMinimized) { applyMinimizedOffset(stackBounds, getMovementBounds(stackBounds)); @@ -216,6 +216,14 @@ class PinnedStackController { return stackBounds; } + /** + * @return the size of the PIP based on the given {@param aspectRatio}. + */ + Size getSize(float aspectRatio) { + return mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, mMinSize, + mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight); + } + /** * @return the default bounds to show the PIP when there is no active PIP. */ @@ -224,8 +232,9 @@ class PinnedStackController { getInsetBounds(insetBounds); final Rect defaultBounds = new Rect(); - Gravity.apply(mDefaultStackGravity, mDefaultStackSize.getWidth(), - mDefaultStackSize.getHeight(), insetBounds, 0, 0, defaultBounds); + final Size size = getSize(mDefaultAspectRatio); + Gravity.apply(mDefaultStackGravity, size.getWidth(), size.getHeight(), insetBounds, 0, 0, + defaultBounds); return defaultBounds; }