Adding PIP logic for phones.

- Adding basic behavior to move PIP window and launch back into
  fullscreen, as well as drag it to dismiss.

Test: Deferring CTS tests as this interaction is only temporary and not
      final

Change-Id: I5272a045090c20c45b345813d10dc385c3f83221
This commit is contained in:
Winson
2016-10-18 18:47:43 -07:00
committed by Winson Chung
parent 4e0b317493
commit 73bc159dcc
11 changed files with 727 additions and 62 deletions

View File

@@ -0,0 +1,24 @@
<!--
Copyright (C) 2016 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="42.0dp"
android:height="42.0dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M38.000000,12.800000l-2.799999,-2.800000 -11.200001,11.200001 -11.200000,-11.200001 -2.800000,2.800000 11.200001,11.200000 -11.200001,11.200001 2.800000,2.799999 11.200000,-11.200001 11.200001,11.200001 2.799999,-2.799999 -11.200001,-11.200001z"/>
</vector>

View File

@@ -0,0 +1,22 @@
<!--
Copyright (C) 2016 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<corners
android:radius="100dp" />
<solid
android:color="#66000000" />
</shape>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pip_dismiss_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/pip_dismiss_background"
android:foreground="@drawable/pip_dismiss"
android:alpha="0"
android:forceHasOverlappingRendering="false" />

View File

@@ -694,4 +694,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. -->
<dimen name="pip_dismiss_target_size">48dp</dimen>
</resources>

View File

@@ -42,7 +42,7 @@ import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.SystemBars;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tv.pip.PipUI;
import com.android.systemui.pip.PipUI;
import com.android.systemui.usb.StorageNotification;
import com.android.systemui.volume.VolumeUI;

View File

@@ -14,38 +14,49 @@
* limitations under the License.
*/
package com.android.systemui.tv.pip;
package com.android.systemui.pip;
import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import com.android.systemui.SystemUI;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
/**
* Controls the picture-in-picture window.
*/
public class PipUI extends SystemUI {
private boolean mSupportPip;
private boolean mSupportsPip;
private boolean mIsLeanBackOnly;
@Override
public void start() {
PackageManager pm = mContext.getPackageManager();
mSupportPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
if (!mSupportPip) {
mSupportsPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
mIsLeanBackOnly = pm.hasSystemFeature(FEATURE_LEANBACK_ONLY);
if (!mSupportsPip) {
return;
}
PipManager pipManager = PipManager.getInstance();
pipManager.initialize(mContext);
if (mIsLeanBackOnly) {
com.android.systemui.tv.pip.PipManager.getInstance().initialize(mContext);
} else {
com.android.systemui.pip.phone.PipManager.getInstance().initialize(mContext);
}
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (!mSupportPip) {
if (!mSupportsPip) {
return;
}
PipManager.getInstance().onConfigurationChanged();
if (mIsLeanBackOnly) {
com.android.systemui.tv.pip.PipManager.getInstance().onConfigurationChanged();
} else {
com.android.systemui.pip.phone.PipManager.getInstance().onConfigurationChanged();
}
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2016 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.systemui.pip.phone;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
import android.view.WindowManager;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
public class PipDismissViewController {
// This delay controls how long to wait before we show the target when the user first moves
// the PIP, to prevent the target from animating if the user just wants to fling the PIP
private static final int SHOW_TARGET_DELAY = 100;
private static final int SHOW_TARGET_DURATION = 200;
private Context mContext;
private WindowManager mWindowManager;
private View mDismissView;
private Rect mDismissTargetScreenBounds = new Rect();
public PipDismissViewController(Context context) {
mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
}
/**
* Creates the dismiss target for showing via {@link #showDismissTarget()}.
*/
public void createDismissTarget() {
if (mDismissView == null) {
// Create a new view for the dismiss target
int dismissTargetSize = mContext.getResources().getDimensionPixelSize(
R.dimen.pip_dismiss_target_size);
LayoutInflater inflater = LayoutInflater.from(mContext);
mDismissView = inflater.inflate(R.layout.pip_dismiss_view, null);
mDismissView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (mDismissView != null) {
mDismissView.getBoundsOnScreen(mDismissTargetScreenBounds);
}
}
});
// Add the target to the window
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
dismissTargetSize,
dismissTargetSize,
WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
lp.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
mWindowManager.addView(mDismissView, lp);
}
mDismissView.animate().cancel();
}
/**
* Shows the dismiss target.
*/
public void showDismissTarget() {
mDismissView.animate()
.alpha(1f)
.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
.setStartDelay(SHOW_TARGET_DELAY)
.setDuration(SHOW_TARGET_DURATION)
.start();
}
/**
* Hides and destroys the dismiss target.
*/
public void destroyDismissTarget() {
if (mDismissView != null) {
mDismissView.animate()
.alpha(0f)
.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
.setStartDelay(0)
.setDuration(SHOW_TARGET_DURATION)
.withEndAction(new Runnable() {
@Override
public void run() {
mWindowManager.removeView(mDismissView);
mDismissView = null;
}
})
.start();
}
}
/**
* @return the dismiss target screen bounds.
*/
public Rect getDismissBounds() {
return mDismissTargetScreenBounds;
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2016 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.systemui.pip.phone;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.content.Context;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
/**
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
public class PipManager {
private static final String TAG = "PipManager";
private static PipManager sPipController;
private Context mContext;
private IActivityManager mActivityManager;
private IWindowManager mWindowManager;
private PipTouchHandler mTouchHandler;
private PipManager() {}
/**
* Initializes {@link PipManager}.
*/
public void initialize(Context context) {
mContext = context;
mActivityManager = ActivityManagerNative.getDefault();
mWindowManager = WindowManagerGlobal.getWindowManagerService();
mTouchHandler = new PipTouchHandler(context, mActivityManager, mWindowManager);
}
/**
* Updates the PIP per configuration changed.
*/
public void onConfigurationChanged() {}
/**
* Gets an instance of {@link PipManager}.
*/
public static PipManager getInstance() {
if (sPipController == null) {
sPipController = new PipManager();
}
return sPipController;
}
}

View File

@@ -0,0 +1,440 @@
/*
* Copyright (C) 2016 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.systemui.pip.phone;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.systemui.recents.misc.Utilities.RECT_EVALUATOR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.animation.Interpolator;
import android.widget.Scroller;
import com.android.internal.os.BackgroundThread;
import com.android.systemui.statusbar.FlingAnimationUtils;
/**
* Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
* the PIP.
*/
public class PipTouchHandler {
private static final String TAG = "PipTouchHandler";
private static final boolean DEBUG_ALLOW_OUT_OF_BOUNDS_STACK = false;
private static final int SNAP_STACK_DURATION = 225;
private static final int DISMISS_STACK_DURATION = 375;
private static final int EXPAND_STACK_DURATION = 225;
private static final float SCROLL_FRICTION_MULTIPLIER = 8f;
private final Context mContext;
private final IActivityManager mActivityManager;
private final ViewConfiguration mViewConfig;
private final InputChannel mInputChannel = new InputChannel();
private final PipInputEventReceiver mInputEventReceiver;
private final PipDismissViewController mDismissViewController;
private final Rect mPinnedStackBounds = new Rect();
private final Rect mBoundedPinnedStackBounds = new Rect();
private ValueAnimator mPinnedStackBoundsAnimator = null;
private final PointF mDownTouch = new PointF();
private final PointF mLastTouch = new PointF();
private boolean mIsDragging;
private int mActivePointerId;
private final FlingAnimationUtils mFlingAnimationUtils;
private final Scroller mScroller;
private VelocityTracker mVelocityTracker;
/**
* Input handler used for Pip windows.
*/
private final class PipInputEventReceiver extends InputEventReceiver {
public PipInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
boolean handled = true;
try {
// To be implemented for input handling over Pip windows
if (event instanceof MotionEvent) {
MotionEvent ev = (MotionEvent) event;
handleTouchEvent(ev);
}
} finally {
finishInputEvent(event, handled);
}
}
}
public PipTouchHandler(Context context, IActivityManager activityManager,
IWindowManager windowManager) {
// Initialize the Pip input consumer
try {
windowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
windowManager.createInputConsumer(INPUT_CONSUMER_PIP, mInputChannel);
} catch (RemoteException e) {
Log.e(TAG, "Failed to create PIP input consumer", e);
}
mContext = context;
mActivityManager = activityManager;
mViewConfig = ViewConfiguration.get(context);
mInputEventReceiver = new PipInputEventReceiver(mInputChannel, Looper.myLooper());
mDismissViewController = new PipDismissViewController(context);
mScroller = new Scroller(context);
mScroller.setFriction(mViewConfig.getScrollFriction() * SCROLL_FRICTION_MULTIPLIER);
mFlingAnimationUtils = new FlingAnimationUtils(context, 2f);
}
private void handleTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
// Cancel any existing animations on the pinned stack
if (mPinnedStackBoundsAnimator != null) {
mPinnedStackBoundsAnimator.cancel();
}
updateBoundedPinnedStackBounds();
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
mActivePointerId = ev.getPointerId(0);
mLastTouch.set(ev.getX(), ev.getY());
mDownTouch.set(mLastTouch);
mIsDragging = false;
// TODO: Consider setting a timer such at after X time, we show the dismiss target
// if the user hasn't already dragged some distance
mDismissViewController.createDismissTarget();
break;
}
case MotionEvent.ACTION_MOVE: {
// Update the velocity tracker
mVelocityTracker.addMovement(ev);
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if (!mIsDragging) {
// Check if the pointer has moved far enough
float movement = PointF.length(mDownTouch.x - ev.getX(activePointerIndex),
mDownTouch.y - ev.getY(activePointerIndex));
if (movement > mViewConfig.getScaledTouchSlop()) {
mIsDragging = true;
mDismissViewController.showDismissTarget();
}
}
if (mIsDragging) {
// Move the pinned stack
float dx = ev.getX(activePointerIndex) - mLastTouch.x;
float dy = ev.getY(activePointerIndex) - mLastTouch.y;
float left = Math.max(mBoundedPinnedStackBounds.left, Math.min(
mBoundedPinnedStackBounds.right, mPinnedStackBounds.left + dx));
float top = Math.max(mBoundedPinnedStackBounds.top, Math.min(
mBoundedPinnedStackBounds.bottom, mPinnedStackBounds.top + dy));
if (DEBUG_ALLOW_OUT_OF_BOUNDS_STACK) {
left = mPinnedStackBounds.left + dx;
top = mPinnedStackBounds.top + dy;
}
movePinnedStack(left, top);
}
mLastTouch.set(ev.getX(), ev.getY());
break;
}
case MotionEvent.ACTION_POINTER_UP: {
// Update the velocity tracker
mVelocityTracker.addMovement(ev);
int pointerIndex = ev.getActionIndex();
int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// Select a new active pointer id and reset the movement state
final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
mLastTouch.set(ev.getX(newPointerIndex), ev.getY(newPointerIndex));
}
break;
}
case MotionEvent.ACTION_UP: {
// Update the velocity tracker
mVelocityTracker.addMovement(ev);
if (mIsDragging) {
mVelocityTracker.computeCurrentVelocity(1000,
ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
float velocityX = mVelocityTracker.getXVelocity();
float velocityY = mVelocityTracker.getYVelocity();
float velocity = PointF.length(velocityX, velocityY);
if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
flingToSnapTarget(velocity, velocityX, velocityY);
} else {
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
int x = (int) ev.getX(activePointerIndex);
int y = (int) ev.getY(activePointerIndex);
Rect dismissBounds = mDismissViewController.getDismissBounds();
if (dismissBounds.contains(x, y)) {
animateDismissPinnedStack(dismissBounds);
} else {
animateToSnapTarget();
}
}
} else {
expandPinnedStackToFullscreen();
}
mDismissViewController.destroyDismissTarget();
// Fall through to clean up
}
case MotionEvent.ACTION_CANCEL: {
mIsDragging = false;
recycleVelocityTracker();
break;
}
}
}
private void initOrResetVelocityTracker() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
} else {
mVelocityTracker.clear();
}
}
private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
/**
* Creates an animation that continues the fling to a snap target.
*/
private void flingToSnapTarget(float velocity, float velocityX, float velocityY) {
mScroller.fling(mPinnedStackBounds.left, mPinnedStackBounds.top,
(int) velocityX, (int) velocityY,
mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.right,
mBoundedPinnedStackBounds.top, mBoundedPinnedStackBounds.bottom);
Rect toBounds = findClosestBoundedPinnedStackSnapTarget(
mScroller.getFinalX(), mScroller.getFinalY());
mScroller.abortAnimation();
if (!mPinnedStackBounds.equals(toBounds)) {
mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
toBounds, 0, FAST_OUT_SLOW_IN);
mFlingAnimationUtils.apply(mPinnedStackBoundsAnimator, 0,
distanceBetweenRectOffsets(mPinnedStackBounds, toBounds),
velocity);
mPinnedStackBoundsAnimator.start();
}
}
/**
* Animates the pinned stack to the closest snap target.
*/
private void animateToSnapTarget() {
Rect toBounds = findClosestBoundedPinnedStackSnapTarget(
mPinnedStackBounds.left, mPinnedStackBounds.top);
if (!mPinnedStackBounds.equals(toBounds)) {
mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
toBounds, SNAP_STACK_DURATION, FAST_OUT_SLOW_IN);
mPinnedStackBoundsAnimator.start();
}
}
/**
* Animates the dismissal of the pinned stack into the given bounds.
*/
private void animateDismissPinnedStack(Rect dismissBounds) {
Rect toBounds = new Rect(dismissBounds.centerX(),
dismissBounds.centerY(),
dismissBounds.centerX() + 1,
dismissBounds.centerY() + 1);
mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
toBounds, DISMISS_STACK_DURATION, FAST_OUT_LINEAR_IN);
mPinnedStackBoundsAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
BackgroundThread.getHandler().post(() -> {
try {
mActivityManager.removeStack(PINNED_STACK_ID);
} catch (RemoteException e) {
Log.e(TAG, "Failed to remove PIP", e);
}
});
}
});
mPinnedStackBoundsAnimator.start();
}
/**
* Resizes the pinned stack back to fullscreen.
*/
private void expandPinnedStackToFullscreen() {
BackgroundThread.getHandler().post(() -> {
try {
mActivityManager.resizeStack(PINNED_STACK_ID, null /* bounds */,
true /* allowResizeInDockedMode */, true /* preserveWindows */,
true /* animate */, EXPAND_STACK_DURATION);
} catch (RemoteException e) {
Log.e(TAG, "Error showing PIP menu activity", e);
}
});
}
/**
* Updates the movement bounds of the pinned stack.
*/
private void updateBoundedPinnedStackBounds() {
try {
StackInfo info = mActivityManager.getStackInfo(PINNED_STACK_ID);
mPinnedStackBounds.set(info.bounds);
mBoundedPinnedStackBounds.set(mActivityManager.getPictureInPictureMovementBounds());
} catch (RemoteException e) {
Log.e(TAG, "Could not fetch PIP movement bounds.", e);
}
}
/**
* Moves the pinned stack to the given {@param left} and {@param top} offsets.
*/
private void movePinnedStack(float left, float top) {
if ((int) left != mPinnedStackBounds.left || (int) top != mPinnedStackBounds.top) {
mPinnedStackBounds.offsetTo((int) left, (int) top);
BackgroundThread.getHandler().post(() -> {
try {
mActivityManager.resizePinnedStack(mPinnedStackBounds,
null /* tempPinnedBounds */);
} catch (RemoteException e) {
Log.e(TAG, "Could not move pinned stack to offset: (" + left + ", " + top + ")",
e);
}
});
}
}
/**
* Resizes the pinned stack to the given {@param bounds}.
*/
private void resizePinnedStack(Rect bounds) {
if (!mPinnedStackBounds.equals(bounds)) {
mPinnedStackBounds.set(bounds);
BackgroundThread.getHandler().post(() -> {
try {
mActivityManager.resizePinnedStack(bounds, null);
} catch (RemoteException e) {
Log.e(TAG, "Could not resize pinned stack to bounds: (" + bounds + ")");
}
});
}
}
/**
* Creates a resize-stack animation.
*/
private ValueAnimator createResizePinnedStackAnimation(Rect toBounds, int duration,
Interpolator interpolator) {
ValueAnimator anim = ValueAnimator.ofObject(RECT_EVALUATOR,
mPinnedStackBounds, toBounds);
anim.setDuration(duration);
anim.setInterpolator(interpolator);
anim.addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
resizePinnedStack((Rect) animation.getAnimatedValue());
}
});
return anim;
}
/**
* @return the closest absolute bounded stack left/top to the given {@param x} and {@param y}.
*/
private Rect findClosestBoundedPinnedStackSnapTarget(int x, int y) {
Point[] snapTargets;
int orientation = mContext.getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
snapTargets = new Point[] {
new Point(mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.top),
new Point(mBoundedPinnedStackBounds.right, mBoundedPinnedStackBounds.top),
new Point(mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.bottom),
new Point(mBoundedPinnedStackBounds.right, mBoundedPinnedStackBounds.bottom)
};
} else {
snapTargets = new Point[] {
new Point(mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.top),
new Point(mBoundedPinnedStackBounds.right, mBoundedPinnedStackBounds.top),
new Point(mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.bottom),
new Point(mBoundedPinnedStackBounds.right, mBoundedPinnedStackBounds.bottom)
};
}
Point closestSnapTarget = null;
float minDistance = Float.MAX_VALUE;
for (Point p : snapTargets) {
float distance = distanceToPoint(p, x, y);
if (distance < minDistance) {
closestSnapTarget = p;
minDistance = distance;
}
}
Rect toBounds = new Rect(mPinnedStackBounds);
toBounds.offsetTo(closestSnapTarget.x, closestSnapTarget.y);
return toBounds;
}
/**
* @return the distance between point {@param p} and the given {@param x} and {@param y}.
*/
private float distanceToPoint(Point p, int x, int y) {
return PointF.length(p.x - x, p.y - y);
}
/**
* @return the distance between points {@param p1} and {@param p2}.
*/
private float distanceBetweenRectOffsets(Rect r1, Rect r2) {
return PointF.length(r1.left - r2.left, r1.top - r2.top);
}
}

View File

@@ -25,7 +25,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Rect;
import android.media.session.MediaController;
@@ -33,16 +32,12 @@ import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.WindowManagerGlobal;
import com.android.systemui.Prefs;
import com.android.systemui.R;
@@ -53,7 +48,6 @@ import java.util.ArrayList;
import java.util.List;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
import static com.android.systemui.Prefs.Key.TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN;
/**
@@ -67,8 +61,6 @@ public class PipManager {
private static PipManager sPipManager;
private static final int MAX_RUNNING_TASKS_COUNT = 10;
/**
* List of package and class name which are considered as Settings,
* so PIP location should be adjusted to the left of the side panel.
@@ -163,9 +155,6 @@ public class PipManager {
private boolean mOnboardingShown;
private String[] mLastPackagesResourceGranted;
private InputChannel mInputChannel;
private PipInputEventReceiver mInputEventReceiver;
private final Runnable mResizePinnedStackRunnable = new Runnable() {
@Override
public void run() {
@@ -203,25 +192,6 @@ public class PipManager {
}
};
/**
* Input handler used for Pip windows. Currently eats all the input events.
*/
private final class PipInputEventReceiver extends InputEventReceiver {
public PipInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
boolean handled = true;
try {
// To be implemented for input handling over Pip windows
} finally {
finishInputEvent(event, handled);
}
}
}
private PipManager() { }
/**
@@ -246,20 +216,6 @@ public class PipManager {
mPipRecentsOverlayManager = new PipRecentsOverlayManager(context);
mMediaSessionManager =
(MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
PackageManager pm = mContext.getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)) {
// Initialize the Pip input consumer
mInputChannel = new InputChannel();
try {
IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
wm.destroyInputConsumer(INPUT_CONSUMER_PIP);
wm.createInputConsumer(INPUT_CONSUMER_PIP, mInputChannel);
mInputEventReceiver = new PipInputEventReceiver(mInputChannel, Looper.myLooper());
} catch (RemoteException e) {
Log.e(TAG, "Failed to create PIP input consumer", e);
}
}
}
private void loadConfigurationsAndApply() {
@@ -290,7 +246,7 @@ public class PipManager {
/**
* Updates the PIP per configuration changed.
*/
void onConfigurationChanged() {
public void onConfigurationChanged() {
loadConfigurationsAndApply();
mPipRecentsOverlayManager.onConfigurationChanged(mContext);
}
@@ -350,11 +306,7 @@ public class PipManager {
*/
private void showPipOverlay() {
if (DEBUG) Log.d(TAG, "showPipOverlay()");
// Temporary workaround to prevent the overlay on phones
PackageManager pm = mContext.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)) {
PipOverlayActivity.showPipOverlay(mContext);
}
PipOverlayActivity.showPipOverlay(mContext);
}
/**

View File

@@ -49,7 +49,6 @@ public class PipRecentsControlsView extends FrameLayout {
}
private final PipManager mPipManager = PipManager.getInstance();
private Listener mListener;
private PipControlsView mPipControlsView;
private View mScrim;
private Animator mFocusGainAnimator;