Merge "Updating PIP to match UX"

This commit is contained in:
TreeHugger Robot
2017-01-09 19:33:03 +00:00
committed by Android (Google) Code Review
10 changed files with 89 additions and 273 deletions

View File

@@ -21,7 +21,6 @@ import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.view.Gravity;
import android.view.ViewConfiguration;
import android.widget.Scroller;
@@ -44,9 +43,6 @@ public class PipSnapAlgorithm {
// The friction multiplier to control how slippery the PIP is when flung
private static final float SCROLL_FRICTION_MULTIPLIER = 8f;
// The fraction of the stack width to show when minimized
private static final float MINIMIZED_VISIBLE_FRACTION = 0.25f;
private final Context mContext;
private final ArrayList<Integer> mSnapGravities = new ArrayList<>();
@@ -56,8 +52,12 @@ public class PipSnapAlgorithm {
private Scroller mScroller;
private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
private final int mMinimizedVisibleSize;
public PipSnapAlgorithm(Context context) {
mContext = context;
mMinimizedVisibleSize = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.pip_minimized_visible_size);
onConfigurationChanged();
}
@@ -129,11 +129,10 @@ public class PipSnapAlgorithm {
* Applies the offset to the {@param stackBounds} to adjust it to a minimized state.
*/
public void applyMinimizedOffset(Rect stackBounds, Rect movementBounds, Point displaySize) {
int visibleWidth = (int) (MINIMIZED_VISIBLE_FRACTION * stackBounds.width());
if (stackBounds.left <= movementBounds.centerX()) {
stackBounds.offsetTo(-stackBounds.width() + visibleWidth, stackBounds.top);
stackBounds.offsetTo(-stackBounds.width() + mMinimizedVisibleSize, stackBounds.top);
} else {
stackBounds.offsetTo(displaySize.x - visibleWidth, stackBounds.top);
stackBounds.offsetTo(displaySize.x - mMinimizedVisibleSize, stackBounds.top);
}
}
@@ -220,15 +219,23 @@ public class PipSnapAlgorithm {
* the new bounds out to {@param boundsOut}.
*/
private void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut) {
final int fromLeft = Math.abs(stackBounds.left - movementBounds.left);
final int fromTop = Math.abs(stackBounds.top - movementBounds.top);
final int fromRight = Math.abs(movementBounds.right - stackBounds.left);
final int fromBottom = Math.abs(movementBounds.bottom - stackBounds.top);
// If the stackBounds are minimized, then it should only be snapped back horizontally
final int boundedLeft = Math.max(movementBounds.left, Math.min(movementBounds.right,
stackBounds.left));
final int boundedTop = Math.max(movementBounds.top, Math.min(movementBounds.bottom,
stackBounds.top));
boundsOut.set(stackBounds);
if (stackBounds.left < movementBounds.left ||
stackBounds.left > movementBounds.right) {
boundsOut.offsetTo(boundedLeft, boundsOut.top);
return;
}
// Otherwise, just find the closest edge
final int fromLeft = Math.abs(stackBounds.left - movementBounds.left);
final int fromTop = Math.abs(stackBounds.top - movementBounds.top);
final int fromRight = Math.abs(movementBounds.right - stackBounds.left);
final int fromBottom = Math.abs(movementBounds.bottom - stackBounds.top);
if (fromLeft <= fromTop && fromLeft <= fromRight && fromLeft <= fromBottom) {
boundsOut.offsetTo(movementBounds.left, boundedTop);
} else if (fromTop <= fromLeft && fromTop <= fromRight && fromTop <= fromBottom) {

View File

@@ -64,6 +64,9 @@
when the user opens homescreen. -->
<dimen name="docked_stack_minimize_thickness">8dp</dimen>
<!-- The amount to leave on-screen when the PIP is minimized. -->
<dimen name="pip_minimized_visible_size">48dp</dimen>
<!-- Min width for a tablet device -->
<dimen name="min_xlarge_screen_width">800dp</dimen>

View File

@@ -1542,6 +1542,7 @@
<java-symbol type="dimen" name="docked_stack_divider_thickness" />
<java-symbol type="dimen" name="docked_stack_divider_insets" />
<java-symbol type="dimen" name="docked_stack_minimize_thickness" />
<java-symbol type="dimen" name="pip_minimized_visible_size" />
<java-symbol type="integer" name="config_dockedStackDividerSnapMode" />
<java-symbol type="fraction" name="docked_stack_divider_fixed_ratio" />
<java-symbol type="fraction" name="thumbnail_fullscreen_scale" />

View File

@@ -13,32 +13,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<FrameLayout
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?android:selectableItemBackground">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<ImageView
android:id="@+id/icon"
android:layout_width="@dimen/pip_menu_action_icon_size"
android:layout_height="@dimen/pip_menu_action_icon_size"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="4dp" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:gravity="center"
android:textSize="12sp"
android:textColor="#ffffffff"
android:textAllCaps="true"
android:fontFamily="sans-serif" />
</LinearLayout>
</FrameLayout>
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="10dp"
android:background="?android:selectableItemBackgroundBorderless" />

View File

@@ -13,47 +13,38 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#66000000">
android:background="#00000000">
<!-- The above background is only for the dismiss button ripple to show. -->
<LinearLayout
android:id="@+id/actions"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="48dp">
</LinearLayout>
<ImageView
android:id="@+id/dismiss"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="top|end"
android:padding="10dp"
android:contentDescription="@string/pip_phone_dismiss"
android:src="@drawable/ic_close_white"
android:background="?android:selectableItemBackgroundBorderless" />
<LinearLayout
<FrameLayout
android:id="@+id/actions_container"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="bottom"
android:orientation="horizontal">
<TextView
android:id="@+id/dismiss"
android:layout_width="0dp"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:background="#66000000"
android:visibility="invisible">
<LinearLayout
android:id="@+id/actions"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:textSize="12sp"
android:textColor="#ffffffff"
android:fontFamily="sans-serif"
android:text="@string/pip_phone_dismiss"
android:background="?android:selectableItemBackground" />
<TextView
android:id="@+id/expand"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:textSize="12sp"
android:textColor="#ffffffff"
android:fontFamily="sans-serif"
android:text="@string/pip_phone_expand"
android:background="?android:selectableItemBackground" />
</LinearLayout>
android:layout_gravity="center_horizontal"
android:orientation="horizontal" />
</FrameLayout>
</FrameLayout>

View File

@@ -724,10 +724,7 @@
<!-- The alpha to apply to the recents row when it doesn't have focus -->
<item name="recents_recents_row_dim_alpha" format="float" type="dimen">0.5</item>
<!-- The size of the PIP dismiss target. -->
<!-- The size of the PIP drag-to-dismiss target. -->
<dimen name="pip_dismiss_target_size">48dp</dimen>
<!-- The size of a PIP menu action icon. -->
<dimen name="pip_menu_action_icon_size">32dp</dimen>
</resources>

View File

@@ -1704,14 +1704,6 @@
not appear on production builds ever. -->
<string name="picture_in_picture" translatable="false">Picture-in-Picture</string>
<!-- PIP swipe to dismiss title. Non-translatable since it should
not appear on production builds ever. -->
<string name="pip_swipe_to_dismiss_title" translatable="false">Swipe to dismiss</string>
<!-- PIP swipe to dismiss description. Non-translatable since it should
not appear on production builds ever. -->
<string name="pip_swipe_to_dismiss_summary" translatable="false">Swipe left or right off screen to close the PIP</string>
<!-- PIP drag to dismiss title. Non-translatable since it should
not appear on production builds ever. -->
<string name="pip_drag_to_dismiss_title" translatable="false">Drag to dismiss</string>
@@ -1720,22 +1712,6 @@
not appear on production builds ever. -->
<string name="pip_drag_to_dismiss_summary" translatable="false">Drag to the dismiss target at the bottom of the screen to close the PIP</string>
<!-- PIP tap once to break through to the activity title. Non-translatable since it should
not appear on production builds ever. -->
<string name="pip_tap_through_title" translatable="false">Tap to interact</string>
<!-- PIP tap once to break through to the activity description. Non-translatable since it should
not appear on production builds ever. -->
<string name="pip_tap_through_summary" translatable="false">Tap once to interact with the activity</string>
<!-- PIP snap to closest edge title. Non-translatable since it should
not appear on production builds ever. -->
<string name="pip_snap_mode_edge_title" translatable="false">Snap to closest edge</string>
<!-- PIP snap to closest edge description. Non-translatable since it should
not appear on production builds ever. -->
<string name="pip_snap_mode_edge_summary" translatable="false">Snap to the closest edge</string>
<!-- PIP allow minimize title. Non-translatable since it should
not appear on production builds ever. -->
<string name="pip_allow_minimize_title" translatable="false">Allow PIP to minimize</string>

View File

@@ -125,35 +125,17 @@
android:key="picture_in_picture"
android:title="@string/picture_in_picture">
<com.android.systemui.tuner.TunerSwitch
android:key="pip_swipe_to_dismiss"
android:title="@string/pip_swipe_to_dismiss_title"
android:summary="@string/pip_swipe_to_dismiss_summary"
sysui:defValue="true" />
<com.android.systemui.tuner.TunerSwitch
android:key="pip_drag_to_dismiss"
android:title="@string/pip_drag_to_dismiss_title"
android:summary="@string/pip_drag_to_dismiss_summary"
sysui:defValue="true" />
<com.android.systemui.tuner.TunerSwitch
android:key="pip_tap_through"
android:title="@string/pip_tap_through_title"
android:summary="@string/pip_tap_through_summary"
sysui:defValue="false" />
<com.android.systemui.tuner.TunerSwitch
android:key="pip_snap_mode_edge"
android:title="@string/pip_snap_mode_edge_title"
android:summary="@string/pip_snap_mode_edge_summary"
sysui:defValue="false" />
sysui:defValue="false" />
<com.android.systemui.tuner.TunerSwitch
android:key="pip_allow_minimize"
android:title="@string/pip_allow_minimize_title"
android:summary="@string/pip_allow_minimize_summary"
sysui:defValue="false" />
sysui:defValue="true" />
</PreferenceScreen>

View File

@@ -66,7 +66,6 @@ public class PipMenuActivity extends Activity {
private final List<RemoteAction> mActions = new ArrayList<>();
private View mMenuContainer;
private View mDismissButton;
private View mExpandButton;
private ObjectAnimator mMenuContainerAnimator;
@@ -126,10 +125,6 @@ public class PipMenuActivity extends Activity {
mDismissButton.setOnClickListener((v) -> {
dismissPip();
});
mExpandButton = findViewById(R.id.expand);
mExpandButton.setOnClickListener((v) -> {
expandPip();
});
notifyActivityCallback(mMessenger);
showMenu();
@@ -245,29 +240,35 @@ public class PipMenuActivity extends Activity {
}
private void updateActionViews() {
ViewGroup actionsContainer = (ViewGroup) findViewById(R.id.actions);
if (actionsContainer != null) {
actionsContainer.removeAllViews();
ViewGroup actionsContainer = (ViewGroup) findViewById(R.id.actions_container);
actionsContainer.setOnTouchListener((v, ev) -> {
// Do nothing, prevent click through to parent
return true;
});
// Recreate the layout
final LayoutInflater inflater = LayoutInflater.from(this);
for (int i = 0; i < mActions.size(); i++) {
final RemoteAction action = mActions.get(i);
final ViewGroup actionContainer = (ViewGroup) inflater.inflate(
R.layout.pip_menu_action, actionsContainer, false);
actionContainer.setOnClickListener((v) -> {
action.sendActionInvoked();
});
if (mActions.isEmpty()) {
actionsContainer.setVisibility(View.INVISIBLE);
} else {
actionsContainer.setVisibility(View.VISIBLE);
ViewGroup actionsGroup = (ViewGroup) findViewById(R.id.actions);
if (actionsGroup != null) {
actionsGroup.removeAllViews();
final TextView title = (TextView) actionContainer.findViewById(R.id.title);
title.setText(action.getTitle());
title.setContentDescription(action.getContentDescription());
final ImageView icon = (ImageView) actionContainer.findViewById(R.id.icon);
action.getIcon().loadDrawableAsync(this, (d) -> {
icon.setImageDrawable(d);
}, mHandler);
actionsContainer.addView(actionContainer);
// Recreate the layout
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);
action.getIcon().loadDrawableAsync(this, d -> {
actionView.setImageDrawable(d);
}, mHandler);
actionView.setContentDescription(action.getContentDescription());
actionView.setOnClickListener(v -> {
action.sendActionInvoked();
});
actionsGroup.addView(actionView);
}
}
}
}

View File

@@ -58,10 +58,7 @@ public class PipTouchHandler implements TunerService.Tunable {
private static final String TAG = "PipTouchHandler";
private static final boolean DEBUG_ALLOW_OUT_OF_BOUNDS_STACK = false;
private static final String TUNER_KEY_SWIPE_TO_DISMISS = "pip_swipe_to_dismiss";
private static final String TUNER_KEY_DRAG_TO_DISMISS = "pip_drag_to_dismiss";
private static final String TUNER_KEY_TAP_THROUGH = "pip_tap_through";
private static final String TUNER_KEY_SNAP_MODE_EDGE = "pip_snap_mode_edge";
private static final String TUNER_KEY_ALLOW_MINIMIZE = "pip_allow_minimize";
private static final int SNAP_STACK_DURATION = 225;
@@ -71,8 +68,6 @@ public class PipTouchHandler implements TunerService.Tunable {
// The fraction of the stack width that the user has to drag offscreen to minimize the PIP
private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.15f;
// The fraction of the stack width that the user has to move when flinging to dismiss the PIP
private static final float DISMISS_FLING_DISTANCE_FRACTION = 0.3f;
private final Context mContext;
private final IActivityManager mActivityManager;
@@ -87,16 +82,10 @@ public class PipTouchHandler implements TunerService.Tunable {
private final PipSnapAlgorithm mSnapAlgorithm;
private PipMotionHelper mMotionHelper;
// Allow swiping offscreen to dismiss the PIP
private boolean mEnableSwipeToDismiss = true;
// Allow dragging the PIP to a location to close it
private boolean mEnableDragToDismiss = true;
// Allow tapping on the PIP to show additional controls
private boolean mEnableTapThrough = false;
// Allow snapping the PIP to the closest edge and not the corners of the screen
private boolean mEnableSnapToEdge = false;
private boolean mEnableDragToDismiss = false;
// Allow the PIP to be "docked" slightly offscreen
private boolean mEnableMinimizing = false;
private boolean mEnableMinimizing = true;
private final Rect mPinnedStackBounds = new Rect();
private final Rect mBoundedPinnedStackBounds = new Rect();
@@ -192,16 +181,15 @@ public class PipTouchHandler implements TunerService.Tunable {
mSnapAlgorithm = new PipSnapAlgorithm(mContext);
mTouchState = new PipTouchState(mViewConfig);
mFlingAnimationUtils = new FlingAnimationUtils(context, 2f);
mGestures = new PipTouchGesture[]{
mDragToDismissGesture, mSwipeToDismissGesture, mTapThroughGesture, mMinimizeGesture,
mDefaultMovementGesture
mGestures = new PipTouchGesture[] {
mDragToDismissGesture, mTapThroughGesture, mMinimizeGesture, mDefaultMovementGesture
};
mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler());
registerInputConsumer();
setSnapToEdge(true);
// Register any tuner settings changes
TunerService.get(context).addTunable(this, TUNER_KEY_SWIPE_TO_DISMISS,
TUNER_KEY_DRAG_TO_DISMISS, TUNER_KEY_TAP_THROUGH, TUNER_KEY_SNAP_MODE_EDGE,
TunerService.get(context).addTunable(this, TUNER_KEY_DRAG_TO_DISMISS,
TUNER_KEY_ALLOW_MINIMIZE);
}
@@ -209,34 +197,18 @@ public class PipTouchHandler implements TunerService.Tunable {
public void onTuningChanged(String key, String newValue) {
if (newValue == null) {
// Reset back to default
mEnableSwipeToDismiss = true;
mEnableDragToDismiss = true;
mEnableMinimizing = false;
mEnableDragToDismiss = false;
mEnableMinimizing = true;
setMinimizedState(false);
mEnableTapThrough = false;
mIsTappingThrough = false;
mEnableSnapToEdge = false;
setSnapToEdge(false);
return;
}
switch (key) {
case TUNER_KEY_SWIPE_TO_DISMISS:
mEnableSwipeToDismiss = Integer.parseInt(newValue) != 0;
break;
case TUNER_KEY_DRAG_TO_DISMISS:
mEnableDragToDismiss = Integer.parseInt(newValue) != 0;
break;
case TUNER_KEY_ALLOW_MINIMIZE:
mEnableMinimizing = Integer.parseInt(newValue) != 0;
break;
case TUNER_KEY_TAP_THROUGH:
mEnableTapThrough = Integer.parseInt(newValue) != 0;
mIsTappingThrough = false;
break;
case TUNER_KEY_SNAP_MODE_EDGE:
mEnableSnapToEdge = Integer.parseInt(newValue) != 0;
setSnapToEdge(mEnableSnapToEdge);
break;
}
}
@@ -480,45 +452,6 @@ public class PipTouchHandler implements TunerService.Tunable {
}
}
/**
* @return whether the velocity is coincident with the current pinned stack bounds to be
* considered a fling to dismiss.
*/
private boolean isFlingToDismiss(float velocityX) {
Point displaySize = new Point();
mContext.getDisplay().getRealSize(displaySize);
return (mPinnedStackBounds.right > displaySize.x && velocityX > 0) ||
(mPinnedStackBounds.left < 0 && velocityX < 0);
}
/**
* Flings the PIP to dismiss it offscreen.
*/
private void flingToDismiss(float velocityX) {
Point displaySize = new Point();
mContext.getDisplay().getRealSize(displaySize);
float offsetX = velocityX > 0
? displaySize.x + mPinnedStackBounds.width()
: -mPinnedStackBounds.width();
Rect toBounds = new Rect(mPinnedStackBounds);
toBounds.offsetTo((int) offsetX, toBounds.top);
if (!mPinnedStackBounds.equals(toBounds)) {
mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds,
toBounds, 0, FAST_OUT_SLOW_IN, mUpdatePinnedStackBoundsListener);
mFlingAnimationUtils.apply(mPinnedStackBoundsAnimator, 0,
distanceBetweenRectOffsets(mPinnedStackBounds, toBounds),
velocityX);
mPinnedStackBoundsAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
BackgroundThread.getHandler().post(PipTouchHandler.this::dismissPinnedStack);
}
});
mPinnedStackBoundsAnimator.start();
}
}
/**
* Animates the dismissal of the PIP over the dismiss target bounds.
*/
@@ -642,57 +575,6 @@ public class PipTouchHandler implements TunerService.Tunable {
/**** Gestures ****/
/**
* Gesture controlling swiping offscreen to dismiss the PIP.
*/
private PipTouchGesture mSwipeToDismissGesture = new PipTouchGesture() {
@Override
boolean onMove(PipTouchState touchState) {
if (mEnableSwipeToDismiss) {
boolean isDraggingOffscreen = isDraggingOffscreen(touchState);
if (touchState.startedDragging() && isDraggingOffscreen) {
// Reset the minimized state once we drag horizontally
setMinimizedState(false);
}
if (touchState.allowDraggingOffscreen() && isDraggingOffscreen) {
// Move the pinned stack, but ignore the vertical movement
float left = mPinnedStackBounds.left + touchState.getLastTouchDelta().x;
mTmpBounds.set(mPinnedStackBounds);
mTmpBounds.offsetTo((int) left, mPinnedStackBounds.top);
if (!mTmpBounds.equals(mPinnedStackBounds)) {
mPinnedStackBounds.set(mTmpBounds);
mMotionHelper.resizeToBounds(mPinnedStackBounds);
}
return true;
}
}
return false;
}
@Override
public boolean onUp(PipTouchState touchState) {
if (mEnableSwipeToDismiss && touchState.isDragging()) {
PointF vel = touchState.getVelocity();
PointF downDelta = touchState.getDownTouchDelta();
float minFlingVel = mFlingAnimationUtils.getMinVelocityPxPerSecond();
float flingVelScale = mEnableMinimizing ? 3f : 2f;
if (Math.abs(vel.x) > (flingVelScale * minFlingVel)) {
// Determine if this gesture is actually a fling to dismiss
if (isFlingToDismiss(vel.x) && Math.abs(downDelta.x) >=
(DISMISS_FLING_DISTANCE_FRACTION * mPinnedStackBounds.width())) {
flingToDismiss(vel.x);
} else {
flingToSnapTarget(vel.length(), vel.x, vel.y);
}
return true;
}
}
return false;
}
};
/**
* Gesture controlling dragging the PIP slightly offscreen to minimize it.
*/
@@ -771,8 +653,7 @@ public class PipTouchHandler implements TunerService.Tunable {
@Override
public boolean onUp(PipTouchState touchState) {
if (mEnableTapThrough && !touchState.isDragging() && !mIsMinimized &&
!mIsTappingThrough) {
if (!touchState.isDragging() && !mIsMinimized && !mIsTappingThrough) {
mMenuController.showMenu();
mIsTappingThrough = true;
return true;