Creating PinnedStackController.
- Creating a PinnedStackController to keep track of the state of the PIP
to prevent changes in the system (ie. IME showing) and user interaction
from clobbering each other.
- Refactoring calls in AM into WM/controller
Test: android.server.cts.ActivityManagerPinnedStackTests
Change-Id: Ie59dfd45d5c54764ba69a589b3b8148845e92cc3
Signed-off-by: Winson Chung <winsonc@google.com>
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");
}