* Returns a list that contains the memory stats for currently running processes.
*/
public abstract List<ProcessMemoryState> getMemoryStateForProcesses();
+
+ /**
+ * This enforces {@code func} can only be called if either the caller is Recents activity or
+ * has {@code permission}.
+ */
+ public abstract void enforceCallerIsRecentsOrHasPermission(String permission, String func);
}
* the WM has changed in the mean time but the client has not received onMovementBoundsChanged).
*/
void onMovementBoundsChanged(in Rect insetBounds, in Rect normalBounds, in Rect animatingBounds,
- boolean fromImeAdjustement, int displayRotation);
+ boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation);
/**
* Called when window manager decides to adjust the pinned stack bounds because of the IME, or
* when the listener is first registered to allow the listener to synchronized its state with
* the controller. This call will always be followed by a onMovementBoundsChanged() call
- * with fromImeAdjustement set to true.
+ * with fromImeAdjustement set to {@code true}.
*/
void onImeVisibilityChanged(boolean imeVisible, int imeHeight);
/**
+ * Called when window manager decides to adjust the pinned stack bounds because of the shelf, or
+ * when the listener is first registered to allow the listener to synchronized its state with
+ * the controller. This call will always be followed by a onMovementBoundsChanged() call
+ * with fromShelfAdjustment set to {@code true}.
+ */
+ void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight);
+
+ /**
* Called when window manager decides to adjust the minimized state, or when the listener
* is first registered to allow the listener to synchronized its state with the controller.
*/
*/
oneway void setPipVisibility(boolean visible);
- /**
+ /**
+ * Called by System UI to notify of changes to the visibility and height of the shelf.
+ */
+ void setShelfHeight(boolean visible, int shelfHeight);
+
+ /**
* Called by System UI to enable or disable haptic feedback on the navigation bar buttons.
*/
void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled);
boolean hasNavigationBar();
/**
- * Get the position of the nav bar
- */
+ * Get the position of the nav bar
+ */
int getNavBarPosition();
/**
* {@param stackBounds}.
*/
public void getMovementBounds(Rect stackBounds, Rect insetBounds, Rect movementBoundsOut,
- int imeHeight) {
+ int bottomOffset) {
// Adjust the right/bottom to ensure the stack bounds never goes offscreen
movementBoundsOut.set(insetBounds);
movementBoundsOut.right = Math.max(insetBounds.left, insetBounds.right -
stackBounds.width());
movementBoundsOut.bottom = Math.max(insetBounds.top, insetBounds.bottom -
stackBounds.height());
- movementBoundsOut.bottom -= imeHeight;
+ movementBoundsOut.bottom -= bottomOffset;
}
/**
<item name="config_pictureInPictureAspectRatioLimitForMinSize" format="float" type="dimen">1.777778</item>
<!-- The default gravity for the picture-in-picture window.
- Currently, this maps to Gravity.TOP | Gravity.RIGHT -->
- <integer name="config_defaultPictureInPictureGravity">0x35</integer>
+ Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
+ <integer name="config_defaultPictureInPictureGravity">0x55</integer>
<!-- The minimum aspect ratio (width/height) that is supported for picture-in-picture. Any
ratio smaller than this is considered too tall and thin to be usable. Currently, this
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
-import android.view.RemoteAnimationAdapter;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
Log.w(TAG, "Failed to enable or disable navigation bar button haptics: ", e);
}
}
+
+ public void setShelfHeight(boolean visible, int shelfHeight) {
+ try {
+ WindowManagerGlobal.getWindowManagerService().setShelfHeight(visible, shelfHeight);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set shelf height");
+ }
+ }
}
}
@Override
+ public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+ mHandler.post(() -> {
+ mTouchHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
+ });
+ }
+
+ @Override
public void onMinimizedStateChanged(boolean isMinimized) {
mHandler.post(() -> {
mTouchHandler.setMinimizedState(isMinimized, true /* fromController */);
@Override
public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
- Rect animatingBounds, boolean fromImeAdjustement, int displayRotation) {
+ Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
+ int displayRotation) {
mHandler.post(() -> {
mTouchHandler.onMovementBoundsChanged(insetBounds, normalBounds, animatingBounds,
- fromImeAdjustement, displayRotation);
+ fromImeAdjustment, fromShelfAdjustment, displayRotation);
});
}
package com.android.systemui.pip.phone;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
private static final int EXPAND_STACK_TO_MENU_DURATION = 250;
private static final int EXPAND_STACK_TO_FULLSCREEN_DURATION = 300;
private static final int MINIMIZE_STACK_MAX_DURATION = 200;
- private static final int IME_SHIFT_DURATION = 300;
+ private static final int SHIFT_DURATION = 300;
// The fraction of the stack width that the user has to drag offscreen to minimize the PiP
private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.3f;
}
/**
- * Animates the PiP to offset it from the IME.
+ * Animates the PiP to offset it from the IME or shelf.
*/
- void animateToIMEOffset(Rect toBounds) {
+ void animateToOffset(Rect toBounds) {
cancelAnimations();
- resizeAndAnimatePipUnchecked(toBounds, IME_SHIFT_DURATION);
+ resizeAndAnimatePipUnchecked(toBounds, SHIFT_DURATION);
}
/**
private boolean mIsImeShowing;
private int mImeHeight;
private int mImeOffset;
+ private boolean mIsShelfShowing;
+ private int mShelfHeight;
private float mSavedSnapFraction = -1f;
private boolean mSendingHoverAccessibilityEvents;
private boolean mMovementWithinMinimize;
mImeHeight = imeHeight;
}
+ public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+ mIsShelfShowing = shelfVisible;
+ mShelfHeight = shelfHeight;
+ }
+
public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect animatingBounds,
- boolean fromImeAdjustement, int displayRotation) {
+ boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) {
+ final int bottomOffset = mIsImeShowing ? mImeHeight : 0;
+
// Re-calculate the expanded bounds
mNormalBounds = normalBounds;
Rect normalMovementBounds = new Rect();
mSnapAlgorithm.getMovementBounds(mNormalBounds, insetBounds, normalMovementBounds,
- mIsImeShowing ? mImeHeight : 0);
+ bottomOffset);
// Calculate the expanded size
float aspectRatio = (float) normalBounds.width() / normalBounds.height();
mExpandedBounds.set(0, 0, expandedSize.getWidth(), expandedSize.getHeight());
Rect expandedMovementBounds = new Rect();
mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds,
- mIsImeShowing ? mImeHeight : 0);
+ bottomOffset);
- // If this is from an IME adjustment, then we should move the PiP so that it is not occluded
- // by the IME
- if (fromImeAdjustement) {
+ // If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
+ // occluded by the IME or shelf.
+ if (fromImeAdjustment || fromShelfAdjustment) {
if (mTouchState.isUserInteracting()) {
// Defer the update of the current movement bounds until after the user finishes
// touching the screen
} else {
- final Rect bounds = new Rect(animatingBounds);
final Rect toMovementBounds = mMenuState == MENU_STATE_FULL
? expandedMovementBounds
: normalMovementBounds;
- if (mIsImeShowing) {
- // IME visible, apply the IME offset if the space allows for it
- final int imeOffset = toMovementBounds.bottom - Math.max(toMovementBounds.top,
- toMovementBounds.bottom - mImeOffset);
- if (bounds.top == mMovementBounds.bottom) {
- // If the PIP is currently resting on top of the IME, then adjust it with
- // the showing IME
- bounds.offsetTo(bounds.left, toMovementBounds.bottom - imeOffset);
- } else {
- bounds.offset(0, Math.min(0, toMovementBounds.bottom - imeOffset
- - bounds.top));
- }
- } else {
- // IME hidden
- if (bounds.top >= (mMovementBounds.bottom - mImeOffset)) {
- // If the PIP is resting on top of the IME, then adjust it with the hiding
- // IME
- bounds.offsetTo(bounds.left, toMovementBounds.bottom);
- }
- }
- mMotionHelper.animateToIMEOffset(bounds);
+ animateToOffset(animatingBounds, toMovementBounds,
+ fromImeAdjustment,
+ fromImeAdjustment ? mIsImeShowing : mIsShelfShowing,
+ // Shelf height serves as an offset, but does not change movement bounds.
+ fromImeAdjustment ? mImeOffset : mShelfHeight);
}
}
}
}
+ private void animateToOffset(Rect animatingBounds, Rect toMovementBounds,
+ boolean fromImeAdjustment, boolean showing, int offset) {
+ final Rect bounds = new Rect(animatingBounds);
+ if (showing) {
+ // IME/shelf visible, apply the IME/shelf offset if the space allows for it
+ final int calculatedOffset = toMovementBounds.bottom - Math.max(toMovementBounds.top,
+ toMovementBounds.bottom - offset);
+ bounds.offset(0,
+ Math.min(0, toMovementBounds.bottom - calculatedOffset - bounds.top));
+ } else {
+ // IME/shelf hidden
+ if (bounds.top >= (mMovementBounds.bottom - offset)) {
+ bounds.offset(0, toMovementBounds.bottom - bounds.top -
+ // Counter going back home from search where keyboard is up.
+ (fromImeAdjustment ? mShelfHeight : 0));
+ }
+ }
+ mMotionHelper.animateToOffset(bounds);
+ }
+
private void onRegistrationChanged(boolean isRegistered) {
mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered
? new PipAccessibilityInteractionConnection(mMotionHelper,
pw.println(innerPrefix + "mIsMinimized=" + mIsMinimized);
pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
+ pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
+ pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction);
pw.println(innerPrefix + "mEnableDragToEdgeDismiss=" + ENABLE_DISMISS_DRAG_TO_EDGE);
pw.println(innerPrefix + "mEnableMinimize=" + ENABLE_MINIMIZE);
}
@Override
+ public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {}
+
+ @Override
public void onMinimizedStateChanged(boolean isMinimized) {}
@Override
public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
- Rect animatingBounds, boolean fromImeAdjustement, int displayRotation) {
+ Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
+ int displayRotation) {
mHandler.post(() -> {
mDefaultPipBounds.set(normalBounds);
});
return mUserController.mMaxRunningUsers;
}
+ @Override
public boolean isCallerRecents(int callingUid) {
return getRecentTasks().isCallerRecents(callingUid);
}
+ @Override
public boolean isRecentsComponentHomeActivity(int userId) {
return getRecentTasks().isRecentsComponentHomeActivity(userId);
}
}
return processMemoryStates;
}
+
+ @Override
+ public void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
+ ActivityManagerService.this.enforceCallerIsRecentsOrHasPermission(permission, func);
+ }
}
/**
private boolean mIsMinimized;
private boolean mIsImeShowing;
private int mImeHeight;
+ private boolean mIsShelfShowing;
+ private int mShelfHeight;
// The set of actions and aspect-ratio for the that are currently allowed on the PiP activity
private ArrayList<RemoteAction> mActions = new ArrayList<>();
listener.onListenerRegistered(mCallbacks);
mPinnedStackListener = listener;
notifyImeVisibilityChanged(mIsImeShowing, mImeHeight);
+ notifyShelfVisibilityChanged(mIsShelfShowing, mShelfHeight);
// The movement bounds notification needs to be sent before the minimized state, since
// SystemUI may use the bounds to retore the minimized position
- notifyMovementBoundsChanged(false /* fromImeAdjustment */);
+ notifyMovementBoundsChanged(false /* fromImeAdjustment */,
+ false /* fromShelfAdjustment */);
notifyActionsChanged(mActions);
notifyMinimizeChanged(mIsMinimized);
} catch (RemoteException e) {
mSnapAlgorithm.applySnapFraction(defaultBounds, movementBounds, snapFraction);
} else {
Gravity.apply(mDefaultStackGravity, size.getWidth(), size.getHeight(), insetBounds,
- 0, mIsImeShowing ? mImeHeight : 0, defaultBounds);
+ 0, Math.max(mIsImeShowing ? mImeHeight : 0,
+ mIsShelfShowing ? mShelfHeight : 0),
+ defaultBounds);
}
return defaultBounds;
}
*/
synchronized void onDisplayInfoChanged() {
mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
- notifyMovementBoundsChanged(false /* fromImeAdjustment */);
+ notifyMovementBoundsChanged(false /* fromImeAdjustment */, false /* fromShelfAdjustment */);
}
/**
// Calculate the stack bounds in the new orientation to the same same fraction along the
// rotated movement bounds.
final Rect postChangeMovementBounds = getMovementBounds(postChangeStackBounds,
- false /* adjustForIme */);
+ false /* adjustForIme */, false /* adjustForShelf */);
mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
snapFraction);
if (mIsMinimized) {
applyMinimizedOffset(postChangeStackBounds, postChangeMovementBounds);
}
- notifyMovementBoundsChanged(false /* fromImeAdjustment */);
+ notifyMovementBoundsChanged(false /* fromImeAdjustment */,
+ false /* fromShelfAdjustment */);
outBounds.set(postChangeStackBounds);
return true;
mIsImeShowing = imeShowing;
mImeHeight = imeHeight;
notifyImeVisibilityChanged(imeShowing, imeHeight);
- notifyMovementBoundsChanged(true /* fromImeAdjustment */);
+ notifyMovementBoundsChanged(true /* fromImeAdjustment */, false /* fromShelfAdjustment */);
+ }
+
+ /**
+ * Sets the shelf state and height.
+ */
+ void setAdjustedForShelf(boolean adjustedForShelf, int shelfHeight) {
+ final boolean shelfShowing = adjustedForShelf && shelfHeight > 0;
+ if (shelfShowing == mIsShelfShowing && shelfHeight == mShelfHeight) {
+ return;
+ }
+
+ mIsShelfShowing = shelfShowing;
+ mShelfHeight = shelfHeight;
+ notifyShelfVisibilityChanged(shelfShowing, shelfHeight);
+ notifyMovementBoundsChanged(false /* fromImeAdjustment */, true /* fromShelfAdjustment */);
}
/**
void setAspectRatio(float aspectRatio) {
if (Float.compare(mAspectRatio, aspectRatio) != 0) {
mAspectRatio = aspectRatio;
- notifyMovementBoundsChanged(false /* fromImeAdjustment */);
+ notifyMovementBoundsChanged(false /* fromImeAdjustment */,
+ false /* fromShelfAdjustment */);
}
}
}
}
+ private void notifyShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+ if (mPinnedStackListener != null) {
+ try {
+ mPinnedStackListener.onShelfVisibilityChanged(shelfVisible, shelfHeight);
+ } catch (RemoteException e) {
+ Slog.e(TAG_WM, "Error delivering bounds changed event.", e);
+ }
+ }
+ }
+
/**
* Notifies listeners that the PIP minimized state has changed.
*/
/**
* Notifies listeners that the PIP movement bounds have changed.
*/
- private void notifyMovementBoundsChanged(boolean fromImeAdjustement) {
+ private void notifyMovementBoundsChanged(boolean fromImeAdjustment,
+ boolean fromShelfAdjustment) {
synchronized (mService.mWindowMap) {
if (mPinnedStackListener == null) {
return;
animatingBounds.set(normalBounds);
}
mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
- animatingBounds, fromImeAdjustement, mDisplayInfo.rotation);
+ animatingBounds, fromImeAdjustment, fromShelfAdjustment,
+ mDisplayInfo.rotation);
} catch (RemoteException e) {
Slog.e(TAG_WM, "Error delivering actions changed event.", e);
}
*/
private Rect getMovementBounds(Rect stackBounds) {
synchronized (mService.mWindowMap) {
- return getMovementBounds(stackBounds, true /* adjustForIme */);
+ return getMovementBounds(stackBounds, true /* adjustForIme */,
+ true /* adjustForShelf */);
}
}
* @return the movement bounds for the given {@param stackBounds} and the current state of the
* controller.
*/
- private Rect getMovementBounds(Rect stackBounds, boolean adjustForIme) {
+ private Rect getMovementBounds(Rect stackBounds, boolean adjustForIme, boolean adjustForShelf) {
synchronized (mService.mWindowMap) {
final Rect movementBounds = new Rect();
getInsetBounds(movementBounds);
// Apply the movement bounds adjustments based on the current state
mSnapAlgorithm.getMovementBounds(stackBounds, movementBounds, movementBounds,
- (adjustForIme && mIsImeShowing) ? mImeHeight : 0);
+ Math.max((adjustForIme && mIsImeShowing) ? mImeHeight : 0,
+ (adjustForShelf && mIsShelfShowing) ? mShelfHeight : 0));
return movementBounds;
}
}
pw.print(prefix + " movementBounds="); getMovementBounds(mTmpRect).printShortString(pw);
pw.println();
pw.println(prefix + " mIsImeShowing=" + mIsImeShowing);
+ pw.println(prefix + " mImeHeight=" + mImeHeight);
+ pw.println(prefix + " mIsShelfShowing=" + mIsShelfShowing);
+ pw.println(prefix + " mShelfHeight=" + mShelfHeight);
pw.println(prefix + " mIsMinimized=" + mIsMinimized);
if (mActions.isEmpty()) {
pw.println(prefix + " mActions=[]");
}
@Override
+ public void setShelfHeight(boolean visible, int shelfHeight) {
+ mAmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.STATUS_BAR,
+ "setShelfHeight()");
+ synchronized (mWindowMap) {
+ getDefaultDisplayContentLocked().getPinnedStackController().setAdjustedForShelf(visible,
+ shelfHeight);
+ }
+ }
+
+ @Override
public void statusBarVisibilityChanged(int visibility) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
!= PackageManager.PERMISSION_GRANTED) {
--- /dev/null
+package com.android.server.wm;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.IPinnedStackListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class PinnedStackControllerTest extends WindowTestsBase {
+
+ @Mock private IPinnedStackListener mIPinnedStackListener;
+ @Mock private IPinnedStackListener.Stub mIPinnedStackListenerStub;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ when(mIPinnedStackListener.asBinder()).thenReturn(mIPinnedStackListenerStub);
+ }
+
+ @Test
+ public void setShelfHeight_shelfVisibilityChangedTriggered() throws RemoteException {
+ sWm.mSupportsPictureInPicture = true;
+ sWm.registerPinnedStackListener(DEFAULT_DISPLAY, mIPinnedStackListener);
+
+ verify(mIPinnedStackListener).onImeVisibilityChanged(false, 0);
+ verify(mIPinnedStackListener).onShelfVisibilityChanged(false, 0);
+ verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
+ eq(false), anyInt());
+ verify(mIPinnedStackListener).onActionsChanged(any());
+ verify(mIPinnedStackListener).onMinimizedStateChanged(anyBoolean());
+
+ reset(mIPinnedStackListener);
+
+ final int SHELF_HEIGHT = 300;
+
+ sWm.setShelfHeight(true, SHELF_HEIGHT);
+ verify(mIPinnedStackListener).onShelfVisibilityChanged(true, SHELF_HEIGHT);
+ verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
+ eq(true), anyInt());
+ verify(mIPinnedStackListener, never()).onImeVisibilityChanged(anyBoolean(), anyInt());
+ }
+}