Merge "Creating PinnedStackController."
diff --git a/Android.mk b/Android.mk
index 4b0587f..db63739 100644
--- a/Android.mk
+++ b/Android.mk
@@ -299,6 +299,8 @@
core/java/android/view/IInputFilter.aidl \
core/java/android/view/IInputFilterHost.aidl \
core/java/android/view/IOnKeyguardExitResult.aidl \
+ core/java/android/view/IPinnedStackController.aidl \
+ core/java/android/view/IPinnedStackListener.aidl \
core/java/android/view/IRotationWatcher.aidl \
core/java/android/view/IWindow.aidl \
core/java/android/view/IWindowFocusObserver.aidl \
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 82cea8a..623a11d 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2940,22 +2940,6 @@
reply.writeNoException();
return true;
}
- case GET_DEFAULT_PICTURE_IN_PICTURE_BOUNDS_TRANSACTION: {
- data.enforceInterface(IActivityManager.descriptor);
- final int displayId = data.readInt();
- Rect r = getDefaultPictureInPictureBounds(displayId);
- reply.writeNoException();
- r.writeToParcel(reply, 0);
- return true;
- }
- case GET_PICTURE_IN_PICTURE_MOVEMENT_BOUNDS_TRANSACTION: {
- data.enforceInterface(IActivityManager.descriptor);
- final int displayId = data.readInt();
- Rect r = getPictureInPictureMovementBounds(displayId);
- reply.writeNoException();
- r.writeToParcel(reply, 0);
- return true;
- }
case SET_VR_MODE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
final IBinder token = data.readStrongBinder();
@@ -7027,36 +7011,6 @@
}
@Override
- public Rect getDefaultPictureInPictureBounds(int displayId) throws RemoteException
- {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- data.writeInterfaceToken(IActivityManager.descriptor);
- data.writeInt(displayId);
- mRemote.transact(GET_DEFAULT_PICTURE_IN_PICTURE_BOUNDS_TRANSACTION, data, reply, 0);
- reply.readException();
- Rect rect = Rect.CREATOR.createFromParcel(reply);
- data.recycle();
- reply.recycle();
- return rect;
- }
-
- @Override
- public Rect getPictureInPictureMovementBounds(int displayId) throws RemoteException
- {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- data.writeInterfaceToken(IActivityManager.descriptor);
- data.writeInt(displayId);
- mRemote.transact(GET_PICTURE_IN_PICTURE_MOVEMENT_BOUNDS_TRANSACTION, data, reply, 0);
- reply.readException();
- Rect rect = Rect.CREATOR.createFromParcel(reply);
- data.recycle();
- reply.recycle();
- return rect;
- }
-
- @Override
public boolean isAppForeground(int uid) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 3f11a7f..7b25c76 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -658,16 +658,6 @@
public void enterPictureInPictureMode(IBinder token) throws RemoteException;
- /**
- * @return the default bounds of the PIP on the default display.
- */
- public Rect getDefaultPictureInPictureBounds(int displayId) throws RemoteException;
-
- /**
- * @return the movement bounds of the PIP on the default display.
- */
- public Rect getPictureInPictureMovementBounds(int displayId) throws RemoteException;
-
public int setVrMode(IBinder token, boolean enabled, ComponentName packageName)
throws RemoteException;
@@ -1112,8 +1102,6 @@
// Start of O transactions
int REQUEST_ACTIVITY_RELAUNCH = IBinder.FIRST_CALL_TRANSACTION+400;
- int GET_DEFAULT_PICTURE_IN_PICTURE_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 401;
- int GET_PICTURE_IN_PICTURE_MOVEMENT_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 402;
- int UPDATE_DISPLAY_OVERRIDE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 403;
- int UNREGISTER_TASK_STACK_LISTENER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+404;
+ int UPDATE_DISPLAY_OVERRIDE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 401;
+ int UNREGISTER_TASK_STACK_LISTENER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+402;
}
diff --git a/core/java/android/view/IPinnedStackController.aidl b/core/java/android/view/IPinnedStackController.aidl
new file mode 100644
index 0000000..830591d
--- /dev/null
+++ b/core/java/android/view/IPinnedStackController.aidl
@@ -0,0 +1,33 @@
+/**
+ * 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 android.view;
+
+import android.graphics.Rect;
+
+/**
+ * An interface to the PinnedStackController to update it of state changes, and to query
+ * information based on the current state.
+ *
+ * @hide
+ */
+interface IPinnedStackController {
+
+ /**
+ * Notifies the controller that the user is currently interacting with the PIP.
+ */
+ oneway void setInInteractiveMode(boolean inInteractiveMode);
+}
diff --git a/core/java/android/view/IPinnedStackListener.aidl b/core/java/android/view/IPinnedStackListener.aidl
new file mode 100644
index 0000000..3050dbb
--- /dev/null
+++ b/core/java/android/view/IPinnedStackListener.aidl
@@ -0,0 +1,39 @@
+/**
+ * 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 android.view;
+
+import android.view.IPinnedStackController;
+
+/**
+ * Listener for changes to the pinned stack made by the WindowManager.
+ *
+ * @hide
+ */
+oneway interface IPinnedStackListener {
+
+ /**
+ * Called when the listener is registered and provides an interface to call back to the pinned
+ * stack controller to update the controller of the pinned stack state.
+ */
+ void onListenerRegistered(IPinnedStackController controller);
+
+ /**
+ * Called when window manager decides to adjust the pinned stack bounds, or when the listener
+ * is first registered to allow the listener to synchronized its state with the controller.
+ */
+ void onBoundsChanged(boolean adjustedForIme);
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index e42b42a..986ff46 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -34,6 +34,7 @@
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.IDockedStackListener;
import android.view.IOnKeyguardExitResult;
+import android.view.IPinnedStackListener;
import android.view.IRotationWatcher;
import android.view.IWindowSession;
import android.view.IWindowSessionCallback;
@@ -426,6 +427,21 @@
void registerDockedStackListener(IDockedStackListener listener);
/**
+ * Registers a listener that will be called when the pinned stack state changes.
+ */
+ void registerPinnedStackListener(int displayId, IPinnedStackListener listener);
+
+ /**
+ * Returns the initial bounds that PIP will be shown when it is first started.
+ */
+ Rect getPictureInPictureDefaultBounds(int displayId);
+
+ /**
+ * Returns the bounds that the PIP can move on the screen in the current PIP state.
+ */
+ Rect getPictureInPictureMovementBounds(int displayId);
+
+ /**
* Updates the dim layer used while resizing.
*
* @param visible Whether the dim layer should be visible.
diff --git a/core/java/com/android/internal/policy/PipMotionHelper.java b/core/java/com/android/internal/policy/PipMotionHelper.java
new file mode 100644
index 0000000..0543442
--- /dev/null
+++ b/core/java/com/android/internal/policy/PipMotionHelper.java
@@ -0,0 +1,91 @@
+/*
+ * 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.internal.policy;
+
+import android.animation.RectEvaluator;
+import android.animation.ValueAnimator;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+/**
+ * A helper to animate the PIP.
+ */
+public class PipMotionHelper {
+
+ private static final String TAG = "PipMotionHelper";
+
+ private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
+ private static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+ private static final int DEFAULT_DURATION = 225;
+
+ private IActivityManager mActivityManager;
+ private Handler mHandler;
+
+ public PipMotionHelper(Handler handler) {
+ mHandler = handler;
+ }
+
+ /**
+ * Moves the PIP to give given {@param bounds}.
+ */
+ public void resizeToBounds(Rect toBounds) {
+ mHandler.post(() -> {
+ if (mActivityManager == null) {
+ mActivityManager = ActivityManagerNative.getDefault();
+ }
+ try {
+ mActivityManager.resizePinnedStack(toBounds, null /* tempPinnedTaskBounds */);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not move pinned stack to bounds: " + toBounds, e);
+ }
+ });
+ }
+
+ /**
+ * Creates an animation to move the PIP to give given {@param toBounds} with the default
+ * animation properties.
+ */
+ public ValueAnimator createAnimationToBounds(Rect fromBounds, Rect toBounds) {
+ return createAnimationToBounds(fromBounds, toBounds, DEFAULT_DURATION, FAST_OUT_SLOW_IN,
+ null);
+ }
+
+ /**
+ * Creates an animation to move the PIP to give given {@param toBounds}.
+ */
+ public ValueAnimator createAnimationToBounds(Rect fromBounds, Rect toBounds, int duration,
+ Interpolator interpolator, ValueAnimator.AnimatorUpdateListener updateListener) {
+ ValueAnimator anim = ValueAnimator.ofObject(RECT_EVALUATOR, fromBounds, toBounds);
+ anim.setDuration(duration);
+ anim.setInterpolator(interpolator);
+ anim.addUpdateListener((ValueAnimator animation) -> {
+ resizeToBounds((Rect) animation.getAnimatedValue());
+ });
+ if (updateListener != null) {
+ anim.addUpdateListener(updateListener);
+ }
+ return anim;
+ }
+
+
+}
diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
index 793b228..1246c27 100644
--- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
@@ -48,21 +48,12 @@
private final ArrayList<Integer> mSnapGravities = new ArrayList<>();
private final int mSnapMode = SNAP_MODE_CORNERS_ONLY;
- private final Scroller mScroller;
- private final Rect mDisplayBounds = new Rect();
+ private Scroller mScroller;
private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
- public PipSnapAlgorithm(Context context, int displayId) {
- final DisplayManager displayManager =
- (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
- final ViewConfiguration viewConfig = ViewConfiguration.get(context);
- final Point displaySize = new Point();
- displayManager.getDisplay(displayId).getRealSize(displaySize);
+ public PipSnapAlgorithm(Context context) {
mContext = context;
- mDisplayBounds.set(0, 0, displaySize.x, displaySize.y);
mOrientation = context.getResources().getConfiguration().orientation;
- mScroller = new Scroller(context);
- mScroller.setFriction(viewConfig.getScrollFriction() * SCROLL_FRICTION_MULTIPLIER);
calculateSnapTargets();
}
@@ -74,7 +65,12 @@
public Rect findClosestSnapBounds(Rect movementBounds, Rect stackBounds, float velocityX,
float velocityY) {
final Rect finalStackBounds = new Rect(stackBounds);
- mScroller.fling(stackBounds.left, stackBounds.top,
+ if (mScroller == null) {
+ final ViewConfiguration viewConfig = ViewConfiguration.get(mContext);
+ mScroller = new Scroller(mContext);
+ mScroller.setFriction(viewConfig.getScrollFriction() * SCROLL_FRICTION_MULTIPLIER);
+ }
+ mScroller.fling(sItackBounds.left, stackBounds.top,
(int) velocityX, (int) velocityY,
movementBounds.left, movementBounds.right,
movementBounds.top, movementBounds.bottom);
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 ffe99c5..7f2d415 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -17,15 +17,16 @@
package com.android.systemui.pip.phone;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.view.Display.DEFAULT_DISPLAY;
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.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager;
import android.content.Context;
@@ -34,6 +35,8 @@
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
+import android.view.IPinnedStackController;
+import android.view.IPinnedStackListener;
import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.InputEvent;
@@ -41,9 +44,9 @@
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
-import android.view.animation.Interpolator;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.policy.PipMotionHelper;
import com.android.internal.policy.PipSnapAlgorithm;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.tuner.TunerService;
@@ -65,12 +68,16 @@
private final Context mContext;
private final IActivityManager mActivityManager;
+ private final IWindowManager mWindowManager;
private final ViewConfiguration mViewConfig;
private final InputChannel mInputChannel = new InputChannel();
+ private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
+ private IPinnedStackController mPinnedStackController;
private final PipInputEventReceiver mInputEventReceiver;
private PipDismissViewController mDismissViewController;
private PipSnapAlgorithm mSnapAlgorithm;
+ private PipMotionHelper mMotionHelper;
private boolean mEnableSwipeToDismiss = true;
private boolean mEnableDragToDismiss = true;
@@ -78,6 +85,13 @@
private final Rect mPinnedStackBounds = new Rect();
private final Rect mBoundedPinnedStackBounds = new Rect();
private ValueAnimator mPinnedStackBoundsAnimator = null;
+ private ValueAnimator.AnimatorUpdateListener mUpdatePinnedStackBoundsListener =
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mPinnedStackBounds.set((Rect) animation.getAnimatedValue());
+ }
+ };
private final PointF mDownTouch = new PointF();
private final PointF mLastTouch = new PointF();
@@ -88,10 +102,13 @@
private final FlingAnimationUtils mFlingAnimationUtils;
private VelocityTracker mVelocityTracker;
+ private final Rect mTmpBounds = new Rect();
+
/**
* Input handler used for Pip windows.
*/
private final class PipInputEventReceiver extends InputEventReceiver {
+
public PipInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@@ -111,6 +128,22 @@
}
}
+ /**
+ * Handler for messages from the PIP controller.
+ */
+ private class PinnedStackListener extends IPinnedStackListener.Stub {
+
+ @Override
+ public void onListenerRegistered(IPinnedStackController controller) {
+ mPinnedStackController = controller;
+ }
+
+ @Override
+ public void onBoundsChanged(boolean adjustedForIme) {
+ // Do nothing
+ }
+ }
+
public PipTouchHandler(Context context, IActivityManager activityManager,
IWindowManager windowManager) {
@@ -118,17 +151,20 @@
try {
windowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
windowManager.createInputConsumer(INPUT_CONSUMER_PIP, mInputChannel);
+ windowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener);
} catch (RemoteException e) {
Log.e(TAG, "Failed to create PIP input consumer", e);
}
mContext = context;
mActivityManager = activityManager;
+ mWindowManager = windowManager;
mViewConfig = ViewConfiguration.get(context);
mInputEventReceiver = new PipInputEventReceiver(mInputChannel, Looper.myLooper());
if (mEnableDragToDismiss) {
mDismissViewController = new PipDismissViewController(context);
}
mFlingAnimationUtils = new FlingAnimationUtils(context, 2f);
+ mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler());
// Register any tuner settings changes
TunerService.get(context).addTunable(this, TUNER_KEY_SWIPE_TO_DISMISS,
@@ -137,12 +173,15 @@
@Override
public void onTuningChanged(String key, String newValue) {
+ if (newValue == null) {
+ return;
+ }
switch (key) {
case TUNER_KEY_SWIPE_TO_DISMISS:
- mEnableSwipeToDismiss = (newValue != null) && Integer.parseInt(newValue) != 0;
+ mEnableSwipeToDismiss = Integer.parseInt(newValue) != 0;
break;
case TUNER_KEY_DRAG_TO_DISMISS:
- mEnableDragToDismiss = (newValue != null) && Integer.parseInt(newValue) != 0;
+ mEnableDragToDismiss = Integer.parseInt(newValue) != 0;
break;
}
}
@@ -152,6 +191,11 @@
}
private void handleTouchEvent(MotionEvent ev) {
+ // Skip touch handling until we are bound to the controller
+ if (mPinnedStackController == null) {
+ return;
+ }
+
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
// Cancel any existing animations on the pinned stack
@@ -166,6 +210,11 @@
mLastTouch.set(ev.getX(), ev.getY());
mDownTouch.set(mLastTouch);
mIsDragging = false;
+ try {
+ mPinnedStackController.setInInteractiveMode(true);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not set dragging state", e);
+ }
if (mEnableDragToDismiss) {
// TODO: Consider setting a timer such at after X time, we show the dismiss
// target if the user hasn't already dragged some distance
@@ -203,8 +252,12 @@
if (mIsSwipingToDismiss) {
// Ignore the vertical movement
- top = mPinnedStackBounds.top;
- movePinnedStack(left, top);
+ mTmpBounds.set(mPinnedStackBounds);
+ mTmpBounds.offsetTo((int) left, mPinnedStackBounds.top);
+ if (!mTmpBounds.equals(mPinnedStackBounds)) {
+ mPinnedStackBounds.set(mTmpBounds);
+ mMotionHelper.resizeToBounds(mPinnedStackBounds);
+ }
} else if (mIsDragging) {
// Move the pinned stack
if (!DEBUG_ALLOW_OUT_OF_BOUNDS_STACK) {
@@ -213,7 +266,12 @@
top = Math.max(mBoundedPinnedStackBounds.top, Math.min(
mBoundedPinnedStackBounds.bottom, top));
}
- movePinnedStack(left, top);
+ mTmpBounds.set(mPinnedStackBounds);
+ mTmpBounds.offsetTo((int) left, (int) top);
+ if (!mTmpBounds.equals(mPinnedStackBounds)) {
+ mPinnedStackBounds.set(mTmpBounds);
+ mMotionHelper.resizeToBounds(mPinnedStackBounds);
+ }
}
mLastTouch.set(ev.getX(), ev.getY());
break;
@@ -275,6 +333,11 @@
case MotionEvent.ACTION_CANCEL: {
mIsDragging = false;
mIsSwipingToDismiss = false;
+ try {
+ mPinnedStackController.setInInteractiveMode(false);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not set dragging state", e);
+ }
recycleVelocityTracker();
break;
}
@@ -303,8 +366,8 @@
Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mBoundedPinnedStackBounds,
mPinnedStackBounds, velocityX, velocityY);
if (!mPinnedStackBounds.equals(toBounds)) {
- mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
- toBounds, 0, FAST_OUT_SLOW_IN);
+ mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds,
+ toBounds, 0, FAST_OUT_SLOW_IN, mUpdatePinnedStackBoundsListener);
mFlingAnimationUtils.apply(mPinnedStackBoundsAnimator, 0,
distanceBetweenRectOffsets(mPinnedStackBounds, toBounds),
velocity);
@@ -319,8 +382,8 @@
Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mBoundedPinnedStackBounds,
mPinnedStackBounds);
if (!mPinnedStackBounds.equals(toBounds)) {
- mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
- toBounds, SNAP_STACK_DURATION, FAST_OUT_SLOW_IN);
+ mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds,
+ toBounds, SNAP_STACK_DURATION, FAST_OUT_SLOW_IN, mUpdatePinnedStackBoundsListener);
mPinnedStackBoundsAnimator.start();
}
}
@@ -335,8 +398,8 @@
Rect toBounds = new Rect(mPinnedStackBounds);
toBounds.offsetTo((int) offsetX, toBounds.top);
if (!mPinnedStackBounds.equals(toBounds)) {
- mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
- toBounds, 0, FAST_OUT_SLOW_IN);
+ mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds,
+ toBounds, 0, FAST_OUT_SLOW_IN, mUpdatePinnedStackBoundsListener);
mFlingAnimationUtils.apply(mPinnedStackBoundsAnimator, 0,
distanceBetweenRectOffsets(mPinnedStackBounds, toBounds),
velocityX);
@@ -364,8 +427,8 @@
dismissBounds.centerY(),
dismissBounds.centerX() + 1,
dismissBounds.centerY() + 1);
- mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
- toBounds, DISMISS_STACK_DURATION, FAST_OUT_LINEAR_IN);
+ mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds,
+ toBounds, DISMISS_STACK_DURATION, FAST_OUT_LINEAR_IN, mUpdatePinnedStackBoundsListener);
mPinnedStackBoundsAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -404,9 +467,9 @@
StackInfo info = mActivityManager.getStackInfo(PINNED_STACK_ID);
if (info != null) {
mPinnedStackBounds.set(info.bounds);
- mBoundedPinnedStackBounds.set(mActivityManager.getPictureInPictureMovementBounds(
+ mBoundedPinnedStackBounds.set(mWindowManager.getPictureInPictureMovementBounds(
info.displayId));
- mSnapAlgorithm = new PipSnapAlgorithm(mContext, info.displayId);
+ mSnapAlgorithm = new PipSnapAlgorithm(mContext);
}
} catch (RemoteException e) {
Log.e(TAG, "Could not fetch PIP movement bounds.", e);
@@ -414,59 +477,6 @@
}
/**
- * 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 distance between points {@param p1} and {@param p2}.
*/
private float distanceBetweenRectOffsets(Rect r1, Rect r2) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 894bc53..c272ae6 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -39,6 +39,8 @@
import android.util.Pair;
import android.view.Display;
import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -135,6 +137,7 @@
private Context mContext;
private PipRecentsOverlayManager mPipRecentsOverlayManager;
private IActivityManager mActivityManager;
+ private IWindowManager mWindowManager;
private MediaSessionManager mMediaSessionManager;
private int mState = STATE_NO_PIP;
private final Handler mHandler = new Handler();
@@ -205,6 +208,7 @@
mContext = context;
mActivityManager = ActivityManagerNative.getDefault();
+ mWindowManager = WindowManagerGlobal.getWindowManagerService();
SystemServicesProxy.getInstance(context).registerTaskStackListener(mTaskStackListener);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
@@ -221,7 +225,7 @@
private void loadConfigurationsAndApply() {
Resources res = mContext.getResources();
try {
- mDefaultPipBounds = mActivityManager.getDefaultPictureInPictureBounds(
+ mDefaultPipBounds = mWindowManager.getPictureInPictureDefaultBounds(
Display.DEFAULT_DISPLAY);
} catch (RemoteException e) {
Log.e(TAG, "Failed to get default PIP bounds", e);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 41d07f2..40f0aae 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1392,12 +1392,6 @@
final long[] mTmpLong = new long[2];
- // The size and position information that describes where the pinned stack will go by default.
- // In particular, the size is defined in DPs.
- Size mDefaultPinnedStackSizeDp;
- Size mDefaultPinnedStackScreenEdgeInsetsDp;
- int mDefaultPinnedStackGravity;
-
static final class ProcessChangeItem {
static final int CHANGE_ACTIVITIES = 1<<0;
static final int CHANGE_PROCESS_STATE = 1<<1;
@@ -7483,7 +7477,8 @@
// current bounds.
final ActivityStack pinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID);
final Rect bounds = (pinnedStack != null)
- ? pinnedStack.mBounds : getDefaultPictureInPictureBounds(DEFAULT_DISPLAY);
+ ? pinnedStack.mBounds
+ : mWindowManager.getPictureInPictureDefaultBounds(DEFAULT_DISPLAY);
mStackSupervisor.moveActivityToPinnedStackLocked(
r, "enterPictureInPictureMode", bounds);
@@ -7493,85 +7488,6 @@
}
}
- @Override
- public Rect getDefaultPictureInPictureBounds(int displayId) {
- final long origId = Binder.clearCallingIdentity();
- final Rect defaultBounds = new Rect();
- try {
- synchronized(this) {
- if (!mSupportsPictureInPicture) {
- return new Rect();
- }
-
- // Convert the sizes to for the current display state
- final DisplayMetrics dm = mStackSupervisor.getDisplayRealMetrics(displayId);
- final int stackWidth = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
- mDefaultPinnedStackSizeDp.getWidth(), dm);
- final int stackHeight = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
- mDefaultPinnedStackSizeDp.getHeight(), dm);
- final Rect maxBounds = new Rect();
- getPictureInPictureBounds(displayId, maxBounds);
- Gravity.apply(mDefaultPinnedStackGravity, stackWidth, stackHeight,
- maxBounds, 0, 0, defaultBounds);
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- return defaultBounds;
- }
-
- @Override
- public Rect getPictureInPictureMovementBounds(int displayId) {
- final long origId = Binder.clearCallingIdentity();
- final Rect maxBounds = new Rect();
- try {
- synchronized(this) {
- if (!mSupportsPictureInPicture) {
- return new Rect();
- }
-
- getPictureInPictureBounds(displayId, maxBounds);
-
- // Adjust the max bounds by the current stack dimensions
- final StackInfo pinnedStackInfo = mStackSupervisor.getStackInfoLocked(
- PINNED_STACK_ID);
- if (pinnedStackInfo != null) {
- maxBounds.right = Math.max(maxBounds.left, maxBounds.right -
- pinnedStackInfo.bounds.width());
- maxBounds.bottom = Math.max(maxBounds.top, maxBounds.bottom -
- pinnedStackInfo.bounds.height());
- }
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- return maxBounds;
- }
-
- /**
- * Calculate the bounds where the pinned stack can move in the current display state.
- */
- private void getPictureInPictureBounds(int displayId, Rect outRect) {
- // Convert the insets to for the current display state
- final DisplayMetrics dm = mStackSupervisor.getDisplayRealMetrics(displayId);
- final int insetsLR = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
- mDefaultPinnedStackScreenEdgeInsetsDp.getWidth(), dm);
- final int insetsTB = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
- mDefaultPinnedStackScreenEdgeInsetsDp.getHeight(), dm);
- try {
- final Point displaySize = mStackSupervisor.getDisplayRealSize(displayId);
- final Rect insets = new Rect();
- mWindowManager.getStableInsets(displayId, insets);
-
- // Calculate the insets from the system decorations and apply the gravity
- outRect.set(insets.left + insetsLR, insets.top + insetsTB,
- displaySize.x - insets.right - insetsLR,
- displaySize.y - insets.bottom - insetsTB);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to calculate PIP movement bounds", e);
- }
- }
-
// =========================================================
// PROCESS INFO
// =========================================================
@@ -13083,7 +12999,6 @@
mLenientBackgroundCheck = lenientBackgroundCheck;
mSupportsLeanbackOnly = supportsLeanbackOnly;
mForceResizableActivities = forceResizable;
- mWindowManager.setForceResizableTasks(mForceResizableActivities);
if (supportsMultiWindow || forceResizable) {
mSupportsMultiWindow = true;
mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable;
@@ -13093,6 +13008,8 @@
mSupportsFreeformWindowManagement = false;
mSupportsPictureInPicture = false;
}
+ mWindowManager.setForceResizableTasks(mForceResizableActivities);
+ mWindowManager.setSupportsPictureInPicture(mSupportsPictureInPicture);
// This happens before any activities are started, so we can change global configuration
// in-place.
updateConfigurationLocked(configuration, null, true);
@@ -13106,12 +13023,6 @@
com.android.internal.R.dimen.thumbnail_width);
mThumbnailHeight = res.getDimensionPixelSize(
com.android.internal.R.dimen.thumbnail_height);
- mDefaultPinnedStackSizeDp = Size.parseSize(res.getString(
- com.android.internal.R.string.config_defaultPictureInPictureSize));
- mDefaultPinnedStackScreenEdgeInsetsDp = Size.parseSize(res.getString(
- com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets));
- mDefaultPinnedStackGravity = res.getInteger(
- com.android.internal.R.integer.config_defaultPictureInPictureGravity);
mAppErrors.loadAppsNotReportingCrashesFromConfigLocked(res.getString(
com.android.internal.R.string.config_appsNotReportingCrashes));
mUserController.mUserSwitchUiEnabled = !res.getBoolean(
@@ -14193,13 +14104,6 @@
}
} else if ("locks".equals(cmd)) {
LockGuard.dump(fd, pw, args);
- } else if ("pip".equals(cmd)) {
- Rect bounds = getDefaultPictureInPictureBounds(DEFAULT_DISPLAY);
- pw.print("defaultBounds="); bounds.printShortString(pw);
- pw.println();
- bounds = getPictureInPictureMovementBounds(DEFAULT_DISPLAY);
- pw.print("movementBounds="); bounds.printShortString(pw);
- pw.println();
} else {
// Dumping a single activity?
if (!dumpActivity(fd, pw, cmd, args, opti, dumpAll, dumpVisibleStacks)) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 7a692b6..1484420 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2498,7 +2498,6 @@
pw.println(" p[rocesses] [PACKAGE_NAME]: process state");
pw.println(" o[om]: out of memory management");
pw.println(" perm[issions]: URI permission grant state");
- pw.println(" pip: PIP state");
pw.println(" prov[iders] [COMP_SPEC ...]: content provider state");
pw.println(" provider [COMP_SPEC]: provider client-side state");
pw.println(" s[ervices] [COMP_SPEC ...]: service state");
@@ -2702,8 +2701,6 @@
pw.println(" Test command for sizing <TASK_ID> by <STEP_SIZE>");
pw.println(" increments within the screen applying the optional [DELAY_MS] between");
pw.println(" each step.");
- pw.println(" pip");
- pw.println(" Gets the current PIP state.");
pw.println(" write");
pw.println(" Write all pending state to storage.");
pw.println();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index e55c1e4..f0427e4 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3420,18 +3420,6 @@
mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_CHANGED, displayId, 0));
}
- DisplayMetrics getDisplayRealMetrics(int displayId) {
- final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
- activityDisplay.mDisplay.getRealMetrics(activityDisplay.mRealMetrics);
- return activityDisplay.mRealMetrics;
- }
-
- Point getDisplayRealSize(int displayId) {
- final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
- activityDisplay.mDisplay.getRealSize(activityDisplay.mRealSize);
- return activityDisplay.mRealSize;
- }
-
private void handleDisplayAdded(int displayId) {
boolean newDisplay;
synchronized (mService) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0b39d65..a99bad2 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -196,6 +196,7 @@
private boolean mDeferredRemoval;
final DockedStackDividerController mDividerControllerLocked;
+ final PinnedStackController mPinnedStackControllerLocked;
final DimLayerController mDimLayerController;
@@ -237,6 +238,7 @@
mService = service;
initializeDisplayBaseInfo();
mDividerControllerLocked = new DockedStackDividerController(service, this);
+ mPinnedStackControllerLocked = new PinnedStackController(service, this);
mDimLayerController = new DimLayerController(this);
// These are the only direct children we should ever have and they are permanent.
@@ -307,6 +309,10 @@
return mDividerControllerLocked;
}
+ PinnedStackController getPinnedStackController() {
+ return mPinnedStackControllerLocked;
+ }
+
/**
* Returns true if the specified UID has access to this display.
*/
@@ -345,6 +351,7 @@
mService.reconfigureDisplayLocked(this);
getDockedDividerController().onConfigurationChanged();
+ getPinnedStackController().onConfigurationChanged();
}
/**
@@ -788,6 +795,7 @@
mDividerControllerLocked.setAdjustedForIme(
false /*ime*/, false /*divider*/, dockVisible /*animate*/, imeWin, imeHeight);
}
+ mPinnedStackControllerLocked.setAdjustedForIme(imeVisible, imeHeight);
}
void setInputMethodAnimLayerAdjustment(int adj) {
@@ -930,6 +938,8 @@
mDimLayerController.dump(prefix + " ", pw);
pw.println();
mDividerControllerLocked.dump(prefix + " ", pw);
+ pw.println();
+ mPinnedStackControllerLocked.dump(prefix + " ", pw);
if (mInputMethodAnimLayerAdjustment != 0) {
pw.println(subPrefix
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
new file mode 100644
index 0000000..a488d52
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -0,0 +1,321 @@
+/*
+ * 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.server.wm;
+
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.animation.ValueAnimator;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Size;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.IPinnedStackController;
+import android.view.IPinnedStackListener;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.policy.PipMotionHelper;
+import com.android.internal.policy.PipSnapAlgorithm;
+
+import java.io.PrintWriter;
+
+/**
+ * Holds the common state of the pinned stack between the system and SystemUI.
+ */
+class PinnedStackController {
+
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM;
+
+ private final WindowManagerService mService;
+ private final DisplayContent mDisplayContent;
+ private final Handler mHandler = new Handler();
+
+ private IPinnedStackListener mPinnedStackListener;
+ private final PinnedStackListenerDeathHandler mPinnedStackListenerDeathHandler =
+ new PinnedStackListenerDeathHandler();
+
+ private final PinnedStackControllerCallback mCallbacks = new PinnedStackControllerCallback();
+ private final PipSnapAlgorithm mSnapAlgorithm;
+ private final PipMotionHelper mMotionHelper;
+
+ // States that affect how the PIP can be manipulated
+ private boolean mInInteractiveMode;
+ private boolean mIsImeShowing;
+ private int mImeHeight;
+ private final Rect mPreImeShowingBounds = new Rect();
+ private ValueAnimator mBoundsAnimator = null;
+
+ // The size and position information that describes where the pinned stack will go by default.
+ private int mDefaultStackGravity;
+ private Size mDefaultStackSize;
+ private Point mScreenEdgeInsets;
+
+ // Temp vars for calculation
+ private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
+ private final Rect mTmpInsets = new Rect();
+
+ /**
+ * The callback object passed to listeners for them to notify the controller of state changes.
+ */
+ private class PinnedStackControllerCallback extends IPinnedStackController.Stub {
+
+ @Override
+ public void setInInteractiveMode(final boolean inInteractiveMode) {
+ mHandler.post(() -> {
+ // Cancel any existing animations on the PIP once the user starts dragging it
+ if (mBoundsAnimator != null && inInteractiveMode) {
+ mBoundsAnimator.cancel();
+ }
+ mInInteractiveMode = inInteractiveMode;
+ mPreImeShowingBounds.setEmpty();
+ });
+ }
+ }
+
+ /**
+ * Handler for the case where the listener dies.
+ */
+ private class PinnedStackListenerDeathHandler implements IBinder.DeathRecipient {
+
+ @Override
+ public void binderDied() {
+ // Clean up the state if the listener dies
+ mInInteractiveMode = false;
+ mPinnedStackListener = null;
+ }
+ }
+
+ PinnedStackController(WindowManagerService service, DisplayContent displayContent) {
+ mService = service;
+ mDisplayContent = displayContent;
+ mSnapAlgorithm = new PipSnapAlgorithm(service.mContext);
+ mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler());
+ reloadResources();
+ }
+
+ void onConfigurationChanged() {
+ reloadResources();
+ }
+
+ /**
+ * Reloads all the resources for the current configuration.
+ */
+ void reloadResources() {
+ final Resources res = mService.mContext.getResources();
+ final Size defaultSizeDp = Size.parseSize(res.getString(
+ com.android.internal.R.string.config_defaultPictureInPictureSize));
+ 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));
+ }
+
+ /**
+ * Registers a pinned stack listener.
+ */
+ void registerPinnedStackListener(IPinnedStackListener listener) {
+ try {
+ listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0);
+ listener.onListenerRegistered(mCallbacks);
+ mPinnedStackListener = listener;
+ notifyBoundsChanged(mIsImeShowing);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register pinned stack listener", e);
+ }
+ }
+
+ /**
+ * @return the default bounds to show the PIP when there is no active PIP.
+ */
+ Rect getDefaultBounds() {
+ final Display display = mDisplayContent.getDisplay();
+ final Rect insetBounds = new Rect();
+ final Point displaySize = new Point();
+ display.getRealSize(displaySize);
+ mService.getStableInsetsLocked(mDisplayContent.getDisplayId(), mTmpInsets);
+ getInsetBounds(displaySize, mTmpInsets, insetBounds);
+
+ final Rect defaultBounds = new Rect();
+ Gravity.apply(mDefaultStackGravity, mDefaultStackSize.getWidth(),
+ mDefaultStackSize.getHeight(), insetBounds, 0, 0, defaultBounds);
+ return defaultBounds;
+ }
+
+ /**
+ * @return the movement bounds for the given {@param stackBounds} and the current state of the
+ * controller.
+ */
+ Rect getMovementBounds(Rect stackBounds) {
+ final Display display = mDisplayContent.getDisplay();
+ final Rect movementBounds = new Rect();
+ final Point displaySize = new Point();
+ display.getRealSize(displaySize);
+ mService.getStableInsetsLocked(mDisplayContent.getDisplayId(), mTmpInsets);
+ getInsetBounds(displaySize, mTmpInsets, movementBounds);
+
+ // Adjust the right/bottom to ensure the stack bounds never goes offscreen
+ movementBounds.right = Math.max(movementBounds.left, movementBounds.right -
+ stackBounds.width());
+ movementBounds.bottom = Math.max(movementBounds.top, movementBounds.bottom -
+ stackBounds.height());
+
+ // Adjust the top if the ime is open
+ if (mIsImeShowing) {
+ movementBounds.bottom -= mImeHeight;
+ }
+
+ return movementBounds;
+ }
+
+ /**
+ * @return the PIP bounds given it's bounds pre-rotation, and post-rotation (with as applied
+ * by the display content, which currently transposes the dimensions but keeps each stack in
+ * the same physical space on the device).
+ */
+ Rect getPostRotationBounds(Rect preRotationStackBounds, Rect postRotationStackBounds) {
+ // Keep the pinned stack in the same aspect ratio as in the old orientation, but
+ // move it into the position in the rotated space, and snap to the closest space
+ // in the new orientation.
+ final Rect movementBounds = getMovementBounds(preRotationStackBounds);
+ final int stackWidth = preRotationStackBounds.width();
+ final int stackHeight = preRotationStackBounds.height();
+ final int left = postRotationStackBounds.centerX() - (stackWidth / 2);
+ final int top = postRotationStackBounds.centerY() - (stackHeight / 2);
+ final Rect postRotBounds = new Rect(left, top, left + stackWidth, top + stackHeight);
+ return mSnapAlgorithm.findClosestSnapBounds(movementBounds, postRotBounds);
+ }
+
+ /**
+ * Sets the Ime state and height.
+ */
+ void setAdjustedForIme(boolean adjustedForIme, int imeHeight) {
+ // Return early if there is no state change
+ if (mIsImeShowing == adjustedForIme && mImeHeight == imeHeight) {
+ return;
+ }
+
+ final Rect stackBounds = new Rect();
+ mService.getStackBounds(PINNED_STACK_ID, stackBounds);
+ final Rect prevMovementBounds = getMovementBounds(stackBounds);
+ final boolean wasAdjustedForIme = mIsImeShowing;
+ mIsImeShowing = adjustedForIme;
+ mImeHeight = imeHeight;
+ if (mInInteractiveMode) {
+ // If the user is currently interacting with the PIP and the ime state changes, then
+ // don't adjust the bounds and defer that to after the interaction
+ notifyBoundsChanged(adjustedForIme /* adjustedForIme */);
+ } else {
+ // Otherwise, we can move the PIP to a sane location to ensure that it does not block
+ // the user from interacting with the IME
+ Rect toBounds;
+ if (!wasAdjustedForIme && adjustedForIme) {
+ // If we are showing the IME, then store the previous bounds
+ mPreImeShowingBounds.set(stackBounds);
+ toBounds = adjustBoundsInMovementBounds(stackBounds);
+ } else if (wasAdjustedForIme && !adjustedForIme) {
+ if (!mPreImeShowingBounds.isEmpty()) {
+ // If we are hiding the IME and the user is not interacting with the PIP, restore
+ // the previous bounds
+ toBounds = mPreImeShowingBounds;
+ } else {
+ if (stackBounds.top == prevMovementBounds.bottom) {
+ // If the PIP is resting on top of the IME, then adjust it with the hiding
+ // of the IME
+ final Rect movementBounds = getMovementBounds(stackBounds);
+ toBounds = new Rect(stackBounds);
+ toBounds.offsetTo(toBounds.left, movementBounds.bottom);
+ } else {
+ // Otherwise, leave the PIP in place
+ toBounds = stackBounds;
+ }
+ }
+ } else {
+ // Otherwise, the IME bounds have changed so we need to adjust the PIP bounds also
+ toBounds = adjustBoundsInMovementBounds(stackBounds);
+ }
+ if (!toBounds.equals(stackBounds)) {
+ if (mBoundsAnimator != null) {
+ mBoundsAnimator.cancel();
+ }
+ mBoundsAnimator = mMotionHelper.createAnimationToBounds(stackBounds, toBounds);
+ mBoundsAnimator.start();
+ }
+ }
+ }
+
+ /**
+ * @return the adjusted {@param stackBounds} such that they are in the movement bounds.
+ */
+ private Rect adjustBoundsInMovementBounds(Rect stackBounds) {
+ final Rect movementBounds = getMovementBounds(stackBounds);
+ final Rect adjustedBounds = new Rect(stackBounds);
+ adjustedBounds.offset(0, Math.min(0, movementBounds.bottom - stackBounds.top));
+ return adjustedBounds;
+ }
+
+ /**
+ * Sends a broadcast that the PIP movement bounds have changed.
+ */
+ private void notifyBoundsChanged(boolean adjustedForIme) {
+ if (mPinnedStackListener != null) {
+ try {
+ mPinnedStackListener.onBoundsChanged(adjustedForIme);
+ } catch (RemoteException e) {
+ Slog.e(TAG_WM, "Error delivering bounds changed event.", e);
+ }
+ }
+ }
+
+ /**
+ * @return the bounds on the screen that the PIP can be visible in.
+ */
+ private void getInsetBounds(Point displaySize, Rect insets, Rect outRect) {
+ outRect.set(insets.left + mScreenEdgeInsets.x, insets.top + mScreenEdgeInsets.y,
+ displaySize.x - insets.right - mScreenEdgeInsets.x,
+ displaySize.y - insets.bottom - mScreenEdgeInsets.y);
+ }
+
+ /**
+ * @return the pixels for a given dp value.
+ */
+ private int dpToPx(float dpValue, DisplayMetrics dm) {
+ return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
+ }
+
+ void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "PinnedStackController");
+ pw.println(prefix + " mIsImeShowing=" + mIsImeShowing);
+ pw.println(prefix + " mInInteractiveMode=" + mInInteractiveMode);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 5637e51..4d8f29d 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -387,24 +387,8 @@
mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
switch (mStackId) {
case PINNED_STACK_ID:
- // Keep the pinned stack in the same aspect ratio as in the old orientation, but
- // move it into the position in the rotated space, and snap to the closest space
- // in the new orientation.
-
- try {
- final IActivityManager am = mService.mActivityManager;
- final Rect movementBounds = am.getPictureInPictureMovementBounds(
- mDisplayContent.getDisplayId());
- final int width = mBounds.width();
- final int height = mBounds.height();
- final int left = mTmpRect2.centerX() - (width / 2);
- final int top = mTmpRect2.centerY() - (height / 2);
- mTmpRect2.set(left, top, left + width, top + height);
-
- final PipSnapAlgorithm snapAlgorithm = new PipSnapAlgorithm(mService.mContext,
- mDisplayContent.getDisplayId());
- mTmpRect2.set(snapAlgorithm.findClosestSnapBounds(movementBounds, mTmpRect2));
- } catch (RemoteException e) {}
+ mTmpRect2 = mDisplayContent.getPinnedStackController().getPostRotationBounds(
+ mBounds, mTmpRect2);
break;
case DOCKED_STACK_ID:
repositionDockedStackAfterRotation(mTmpRect2);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1b08f16..70b0201 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -97,6 +97,7 @@
import android.view.IDockedStackListener;
import android.view.IInputFilter;
import android.view.IOnKeyguardExitResult;
+import android.view.IPinnedStackListener;
import android.view.IRotationWatcher;
import android.view.IWindow;
import android.view.IWindowId;
@@ -166,12 +167,14 @@
import java.util.List;
import static android.Manifest.permission.MANAGE_APP_TOKENS;
+import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.StatusBarManager.DISABLE_MASK;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -529,6 +532,7 @@
private final SparseIntArray mTmpTaskIds = new SparseIntArray();
boolean mForceResizableTasks = false;
+ boolean mSupportsPictureInPicture = false;
int getDragLayerLocked() {
return mPolicy.windowTypeToLayerLw(TYPE_DRAG) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
@@ -3388,6 +3392,36 @@
mDockedStackCreateBounds = bounds;
}
+ @Override
+ public Rect getPictureInPictureDefaultBounds(int displayId) {
+ synchronized (mWindowMap) {
+ if (!mSupportsPictureInPicture) {
+ return new Rect();
+ }
+
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ return displayContent.getPinnedStackController().getDefaultBounds();
+ }
+ }
+
+ @Override
+ public Rect getPictureInPictureMovementBounds(int displayId) {
+ synchronized (mWindowMap) {
+ if (!mSupportsPictureInPicture) {
+ return new Rect();
+ }
+
+ final Rect stackBounds = new Rect();
+ getStackBounds(PINNED_STACK_ID, stackBounds);
+ if (stackBounds.isEmpty()) {
+ return stackBounds;
+ }
+
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ return displayContent.getPinnedStackController().getMovementBounds(stackBounds);
+ }
+ }
+
/**
* Create a new TaskStack and place it on a DisplayContent.
* @param stackId The unique identifier of the new stack.
@@ -8331,6 +8365,7 @@
pw.println(" a[animator]: animator state");
pw.println(" s[essions]: active sessions");
pw.println(" surfaces: active surfaces (debugging enabled only)");
+ pw.println(" pip: PIP state");
pw.println(" d[isplays]: active display contents");
pw.println(" t[okens]: token list");
pw.println(" w[indows]: window list");
@@ -8403,6 +8438,18 @@
pw.println(output.toString());
}
return;
+ } else if ("pip".equals(cmd)) {
+ synchronized(mWindowMap) {
+ pw.print("defaultBounds=");
+ getPictureInPictureDefaultBounds(DEFAULT_DISPLAY).printShortString(pw);
+ pw.println();
+ pw.print("movementBounds=");
+ getPictureInPictureMovementBounds(DEFAULT_DISPLAY).printShortString(pw);
+ pw.println();
+ getDefaultDisplayContentLocked().getPinnedStackController().dump("", pw);
+ pw.println();
+ }
+ return;
} else {
// Dumping a single name?
if (!dumpWindows(pw, cmd, args, opti, dumpAll)) {
@@ -8677,13 +8724,19 @@
}
}
+ public void setSupportsPictureInPicture(boolean supportsPictureInPicture) {
+ synchronized (mWindowMap) {
+ mSupportsPictureInPicture = supportsPictureInPicture;
+ }
+ }
+
static int dipToPixel(int dip, DisplayMetrics displayMetrics) {
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics);
}
@Override
public void registerDockedStackListener(IDockedStackListener listener) {
- if (!checkCallingPermission(android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS,
+ if (!checkCallingPermission(REGISTER_WINDOW_MANAGER_LISTENERS,
"registerDockedStackListener()")) {
return;
}
@@ -8693,6 +8746,21 @@
}
@Override
+ public void registerPinnedStackListener(int displayId, IPinnedStackListener listener) {
+ if (!checkCallingPermission(REGISTER_WINDOW_MANAGER_LISTENERS,
+ "registerPinnedStackListener()")) {
+ return;
+ }
+ if (!mSupportsPictureInPicture) {
+ return;
+ }
+ synchronized (mWindowMap) {
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ displayContent.getPinnedStackController().registerPinnedStackListener(listener);
+ }
+ }
+
+ @Override
public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
try {
WindowState focusedWindow = getFocusedWindow();
@@ -8865,8 +8933,7 @@
@Override
public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
throws RemoteException {
- if (!checkCallingPermission(Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS,
- "registerShortcutKey")) {
+ if (!checkCallingPermission(REGISTER_WINDOW_MANAGER_LISTENERS, "registerShortcutKey")) {
throw new SecurityException(
"Requires REGISTER_WINDOW_MANAGER_LISTENERS permission");
}
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index db5b119..9ec546e 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -591,6 +591,20 @@
}
@Override
+ public void registerPinnedStackListener(int displayId, IPinnedStackListener listener) throws RemoteException {
+ }
+
+ @Override
+ public Rect getPictureInPictureDefaultBounds(int displayId) {
+ return null;
+ }
+
+ @Override
+ public Rect getPictureInPictureMovementBounds(int displayId) {
+ return null;
+ }
+
+ @Override
public void setResizeDimLayer(boolean visible, int targetStackId, float alpha)
throws RemoteException {
}