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:
24
packages/SystemUI/res/drawable/pip_dismiss.xml
Normal file
24
packages/SystemUI/res/drawable/pip_dismiss.xml
Normal 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>
|
||||
22
packages/SystemUI/res/drawable/pip_dismiss_background.xml
Normal file
22
packages/SystemUI/res/drawable/pip_dismiss_background.xml
Normal 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>
|
||||
24
packages/SystemUI/res/layout/pip_dismiss_view.xml
Normal file
24
packages/SystemUI/res/layout/pip_dismiss_view.xml
Normal 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" />
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user