diff options
Diffstat (limited to 'services')
425 files changed, 29635 insertions, 9961 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index e9c9899023b1..ee80daeff87d 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -21,8 +21,13 @@ import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILIT import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_STATUS; import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP; import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CONNECTION; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static android.view.accessibility.AccessibilityInteractionClient.CALL_STACK; +import static android.view.accessibility.AccessibilityInteractionClient.IGNORE_CALL_STACK; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; @@ -31,6 +36,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK import android.accessibilityservice.AccessibilityGestureEvent; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.NonNull; @@ -103,10 +109,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ FingerprintGestureDispatcher.FingerprintGestureClient { private static final boolean DEBUG = false; private static final String LOG_TAG = "AbstractAccessibilityServiceConnection"; - private static final String TRACE_A11Y_SERVICE_CONNECTION = - LOG_TAG + ".IAccessibilityServiceConnection"; - private static final String TRACE_A11Y_SERVICE_CLIENT = - LOG_TAG + ".IAccessibilityServiceClient"; + private static final String TRACE_SVC_CONN = LOG_TAG + ".IAccessibilityServiceConnection"; + private static final String TRACE_SVC_CLIENT = LOG_TAG + ".IAccessibilityServiceClient"; + private static final String TRACE_WM = "WindowManagerInternal"; private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000; protected static final String TAKE_SCREENSHOT = "takeScreenshot"; @@ -298,9 +303,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return false; } try { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onKeyEvent", - keyEvent + ", " + sequenceNumber); + if (svcClientTracingEnabled()) { + logTraceSvcClient("onKeyEvent", keyEvent + ", " + sequenceNumber); } mServiceInterface.onKeyEvent(keyEvent, sequenceNumber); } catch (RemoteException e) { @@ -365,17 +369,16 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setOnKeyEventResult(boolean handled, int sequence) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setOnKeyEventResult", - "handled=" + handled + ";sequence=" + sequence); + if (svcConnTracingEnabled()) { + logTraceSvcConn("setOnKeyEventResult", "handled=" + handled + ";sequence=" + sequence); } mSystemSupport.getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence); } @Override public AccessibilityServiceInfo getServiceInfo() { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getServiceInfo"); + if (svcConnTracingEnabled()) { + logTraceSvcConn("getServiceInfo", ""); } synchronized (mLock) { return mAccessibilityServiceInfo; @@ -393,8 +396,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setServiceInfo(AccessibilityServiceInfo info) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setServiceInfo", "info=" + info); + if (svcConnTracingEnabled()) { + logTraceSvcConn("setServiceInfo", "info=" + info); } final long identity = Binder.clearCallingIdentity(); try { @@ -421,8 +424,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Nullable @Override public AccessibilityWindowInfo.WindowListSparseArray getWindows() { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindows"); + if (svcConnTracingEnabled()) { + logTraceSvcConn("getWindows", ""); } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { @@ -458,8 +461,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public AccessibilityWindowInfo getWindow(int windowId) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindow", "windowId=" + windowId); + if (svcConnTracingEnabled()) { + logTraceSvcConn("getWindow", "windowId=" + windowId); } synchronized (mLock) { int displayId = Display.INVALID_DISPLAY; @@ -496,8 +499,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ long accessibilityNodeId, String viewIdResName, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfosByViewId", + if (svcConnTracingEnabled()) { + logTraceSvcConn("findAccessibilityNodeInfosByViewId", "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" + accessibilityNodeId + ";viewIdResName=" + viewIdResName + ";interactionId=" + interactionId + ";callback=" + callback + ";interrogatingTid=" @@ -539,6 +542,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); final long identityToken = Binder.clearCallingIdentity(); + if (intConnTracingEnabled()) { + logTraceIntConn("findAccessibilityNodeInfosByViewId", + accessibilityNodeId + ";" + viewIdResName + ";" + partialInteractiveRegion + ";" + + interactionId + ";" + callback + ";" + mFetchFlags + ";" + interrogatingPid + + ";" + interrogatingTid + ";" + spec); + } try { connection.getRemote().findAccessibilityNodeInfosByViewId(accessibilityNodeId, viewIdResName, partialInteractiveRegion, interactionId, callback, mFetchFlags, @@ -564,8 +573,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfosByText", + if (svcConnTracingEnabled()) { + logTraceSvcConn("findAccessibilityNodeInfosByText", "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" + accessibilityNodeId + ";text=" + text + ";interactionId=" + interactionId + ";callback=" + callback + ";interrogatingTid=" + interrogatingTid); @@ -606,6 +615,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); final long identityToken = Binder.clearCallingIdentity(); + if (intConnTracingEnabled()) { + logTraceIntConn("findAccessibilityNodeInfosByText", + accessibilityNodeId + ";" + text + ";" + partialInteractiveRegion + ";" + + interactionId + ";" + callback + ";" + mFetchFlags + ";" + interrogatingPid + + ";" + interrogatingTid + ";" + spec); + } try { connection.getRemote().findAccessibilityNodeInfosByText(accessibilityNodeId, text, partialInteractiveRegion, interactionId, callback, mFetchFlags, @@ -631,13 +646,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ int accessibilityWindowId, long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, long interrogatingTid, Bundle arguments) throws RemoteException { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace( - TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfoByAccessibilityId", + if (svcConnTracingEnabled()) { + logTraceSvcConn("findAccessibilityNodeInfoByAccessibilityId", "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" - + accessibilityNodeId + ";interactionId=" + interactionId + ";callback=" - + callback + ";flags=" + flags + ";interrogatingTid=" + interrogatingTid - + ";arguments=" + arguments); + + accessibilityNodeId + ";interactionId=" + interactionId + ";callback=" + + callback + ";flags=" + flags + ";interrogatingTid=" + interrogatingTid + + ";arguments=" + arguments); } final int resolvedWindowId; RemoteAccessibilityConnection connection; @@ -675,6 +689,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); final long identityToken = Binder.clearCallingIdentity(); + if (intConnTracingEnabled()) { + logTraceIntConn("findAccessibilityNodeInfoByAccessibilityId", + accessibilityNodeId + ";" + partialInteractiveRegion + ";" + interactionId + ";" + + callback + ";" + (mFetchFlags | flags) + ";" + interrogatingPid + ";" + + interrogatingTid + ";" + spec + ";" + arguments); + } try { connection.getRemote().findAccessibilityNodeInfoByAccessibilityId( accessibilityNodeId, partialInteractiveRegion, interactionId, callback, @@ -700,12 +720,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findFocus", + if (svcConnTracingEnabled()) { + logTraceSvcConn("findFocus", "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" - + accessibilityNodeId + ";focusType=" + focusType + ";interactionId=" - + interactionId + ";callback=" + callback + ";interrogatingTid=" - + interrogatingTid); + + accessibilityNodeId + ";focusType=" + focusType + ";interactionId=" + + interactionId + ";callback=" + callback + ";interrogatingTid=" + + interrogatingTid); } final int resolvedWindowId; RemoteAccessibilityConnection connection; @@ -743,6 +763,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); final long identityToken = Binder.clearCallingIdentity(); + if (intConnTracingEnabled()) { + logTraceIntConn("findFocus", + accessibilityNodeId + ";" + focusType + ";" + partialInteractiveRegion + ";" + + interactionId + ";" + callback + ";" + mFetchFlags + ";" + interrogatingPid + + ";" + interrogatingTid + ";" + spec); + } try { connection.getRemote().findFocus(accessibilityNodeId, focusType, partialInteractiveRegion, interactionId, callback, mFetchFlags, @@ -768,12 +794,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".focusSearch", + if (svcConnTracingEnabled()) { + logTraceSvcConn("focusSearch", "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" - + accessibilityNodeId + ";direction=" + direction + ";interactionId=" - + interactionId + ";callback=" + callback + ";interrogatingTid=" - + interrogatingTid); + + accessibilityNodeId + ";direction=" + direction + ";interactionId=" + + interactionId + ";callback=" + callback + ";interrogatingTid=" + + interrogatingTid); } final int resolvedWindowId; RemoteAccessibilityConnection connection; @@ -810,6 +836,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); final long identityToken = Binder.clearCallingIdentity(); + if (intConnTracingEnabled()) { + logTraceIntConn("focusSearch", + accessibilityNodeId + ";" + direction + ";" + partialInteractiveRegion + + ";" + interactionId + ";" + callback + ";" + mFetchFlags + ";" + + interrogatingPid + ";" + interrogatingTid + ";" + spec); + } try { connection.getRemote().focusSearch(accessibilityNodeId, direction, partialInteractiveRegion, interactionId, callback, mFetchFlags, @@ -832,17 +864,17 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void sendGesture(int sequence, ParceledListSlice gestureSteps) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".sendGesture", - "sequence=" + sequence + ";gestureSteps=" + gestureSteps); + if (svcConnTracingEnabled()) { + logTraceSvcConn( + "sendGesture", "sequence=" + sequence + ";gestureSteps=" + gestureSteps); } } @Override public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".dispatchGesture", "sequence=" - + sequence + ";gestureSteps=" + gestureSteps + ";displayId=" + displayId); + if (svcConnTracingEnabled()) { + logTraceSvcConn("dispatchGesture", "sequence=" + sequence + ";gestureSteps=" + + gestureSteps + ";displayId=" + displayId); } } @@ -851,12 +883,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ long accessibilityNodeId, int action, Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".performAccessibilityAction", + if (svcConnTracingEnabled()) { + logTraceSvcConn("performAccessibilityAction", "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" - + accessibilityNodeId + ";action=" + action + ";arguments=" + arguments - + ";interactionId=" + interactionId + ";callback=" + callback - + ";interrogatingTid=" + interrogatingTid); + + accessibilityNodeId + ";action=" + action + ";arguments=" + arguments + + ";interactionId=" + interactionId + ";callback=" + callback + + ";interrogatingTid=" + interrogatingTid); } final int resolvedWindowId; synchronized (mLock) { @@ -879,9 +911,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean performGlobalAction(int action) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".performGlobalAction", - "action=" + action); + if (svcConnTracingEnabled()) { + logTraceSvcConn("performGlobalAction", "action=" + action); } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { @@ -893,8 +924,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public @NonNull List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions() { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getSystemActions"); + if (svcConnTracingEnabled()) { + logTraceSvcConn("getSystemActions", ""); } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { @@ -906,9 +937,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean isFingerprintGestureDetectionAvailable() { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace( - TRACE_A11Y_SERVICE_CONNECTION + ".isFingerprintGestureDetectionAvailable"); + if (svcConnTracingEnabled()) { + logTraceSvcConn("isFingerprintGestureDetectionAvailable", ""); } if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { return false; @@ -923,9 +953,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationScale(int displayId) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationScale", - "displayId=" + displayId); + if (svcConnTracingEnabled()) { + logTraceSvcConn("getMagnificationScale", "displayId=" + displayId); } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { @@ -942,9 +971,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public Region getMagnificationRegion(int displayId) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationRegion", - "displayId=" + displayId); + if (svcConnTracingEnabled()) { + logTraceSvcConn("getMagnificationRegion", "displayId=" + displayId); } synchronized (mLock) { final Region region = Region.obtain(); @@ -970,9 +998,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationCenterX(int displayId) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationCenterX", - "displayId=" + displayId); + if (svcConnTracingEnabled()) { + logTraceSvcConn("getMagnificationCenterX", "displayId=" + displayId); } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { @@ -996,9 +1023,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationCenterY(int displayId) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationCenterY", - "displayId=" + displayId); + if (svcConnTracingEnabled()) { + logTraceSvcConn("getMagnificationCenterY", "displayId=" + displayId); } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { @@ -1032,9 +1058,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean resetMagnification(int displayId, boolean animate) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".resetMagnification", - "displayId=" + displayId + ";animate=" + animate); + if (svcConnTracingEnabled()) { + logTraceSvcConn("resetMagnification", "displayId=" + displayId + ";animate=" + animate); } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { @@ -1058,10 +1083,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX, float centerY, boolean animate) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setMagnificationScaleAndCenter", + if (svcConnTracingEnabled()) { + logTraceSvcConn("setMagnificationScaleAndCenter", "displayId=" + displayId + ";scale=" + scale + ";centerX=" + centerX - + ";centerY=" + centerY + ";animate=" + animate); + + ";centerY=" + centerY + ";animate=" + animate); } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { @@ -1087,8 +1112,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setMagnificationCallbackEnabled(int displayId, boolean enabled) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setMagnificationCallbackEnabled", + if (svcConnTracingEnabled()) { + logTraceSvcConn("setMagnificationCallbackEnabled", "displayId=" + displayId + ";enabled=" + enabled); } mInvocationHandler.setMagnificationCallbackEnabled(displayId, enabled); @@ -1100,18 +1125,16 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setSoftKeyboardCallbackEnabled(boolean enabled) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setSoftKeyboardCallbackEnabled", - "enabled=" + enabled); + if (svcConnTracingEnabled()) { + logTraceSvcConn("setSoftKeyboardCallbackEnabled", "enabled=" + enabled); } mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled); } @Override public void takeScreenshot(int displayId, RemoteCallback callback) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".takeScreenshot", - "displayId=" + displayId + ";callback=" + callback); + if (svcConnTracingEnabled()) { + logTraceSvcConn("takeScreenshot", "displayId=" + displayId + ";callback=" + callback); } final long currentTimestamp = SystemClock.uptimeMillis(); if (mRequestTakeScreenshotTimestampMs != 0 @@ -1237,6 +1260,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final long identity = Binder.clearCallingIdentity(); try { final IBinder overlayWindowToken = new Binder(); + if (wmTracingEnabled()) { + logTraceWM("addWindowToken", + overlayWindowToken + ";TYPE_ACCESSIBILITY_OVERLAY;" + displayId + ";null"); + } mWindowManagerService.addWindowToken(overlayWindowToken, TYPE_ACCESSIBILITY_OVERLAY, displayId, null /* options */); synchronized (mLock) { @@ -1263,6 +1290,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ */ public void onDisplayRemoved(int displayId) { final long identity = Binder.clearCallingIdentity(); + if (wmTracingEnabled()) { + logTraceWM( + "addWindowToken", mOverlayWindowTokens.get(displayId) + ";true;" + displayId); + } try { mWindowManagerService.removeWindowToken(mOverlayWindowTokens.get(displayId), true, displayId); @@ -1282,9 +1313,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ */ @Override public IBinder getOverlayWindowToken(int displayId) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getOverlayWindowToken", - "displayId=" + displayId); + if (svcConnTracingEnabled()) { + logTraceSvcConn("getOverlayWindowToken", "displayId=" + displayId); } synchronized (mLock) { return mOverlayWindowTokens.get(displayId); @@ -1299,9 +1329,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ */ @Override public int getWindowIdForLeashToken(@NonNull IBinder token) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindowIdForLeashToken", - "token=" + token); + if (svcConnTracingEnabled()) { + logTraceSvcConn("getWindowIdForLeashToken", "token=" + token); } synchronized (mLock) { return mA11yWindowManager.getWindowIdLocked(token); @@ -1314,8 +1343,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ // Clear the proxy in the other process so this // IAccessibilityServiceConnection can be garbage collected. if (mServiceInterface != null) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".init", "null, " + mId + ", null"); + if (svcClientTracingEnabled()) { + logTraceSvcClient("init", "null, " + mId + ", null"); } mServiceInterface.init(null, mId, null); } @@ -1465,9 +1494,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } try { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityEvent", - event + ";" + serviceWantsEvent); + if (svcClientTracingEnabled()) { + logTraceSvcClient("onAccessibilityEvent", event + ";" + serviceWantsEvent); } listener.onAccessibilityEvent(event, serviceWantsEvent); if (DEBUG) { @@ -1522,9 +1550,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onMagnificationChanged", displayId - + ", " + region + ", " + scale + ", " + centerX + ", " + centerY); + if (svcClientTracingEnabled()) { + logTraceSvcClient("onMagnificationChanged", displayId + ", " + region + ", " + + scale + ", " + centerX + ", " + centerY); } listener.onMagnificationChanged(displayId, region, scale, centerX, centerY); } catch (RemoteException re) { @@ -1541,9 +1569,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onSoftKeyboardShowModeChanged", - String.valueOf(showState)); + if (svcClientTracingEnabled()) { + logTraceSvcClient("onSoftKeyboardShowModeChanged", String.valueOf(showState)); } listener.onSoftKeyboardShowModeChanged(showState); } catch (RemoteException re) { @@ -1557,9 +1584,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityButtonClicked", - String.valueOf(displayId)); + if (svcClientTracingEnabled()) { + logTraceSvcClient("onAccessibilityButtonClicked", String.valueOf(displayId)); } listener.onAccessibilityButtonClicked(displayId); } catch (RemoteException re) { @@ -1579,9 +1605,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace( - TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityButtonAvailabilityChanged", + if (svcClientTracingEnabled()) { + logTraceSvcClient("onAccessibilityButtonAvailabilityChanged", String.valueOf(available)); } listener.onAccessibilityButtonAvailabilityChanged(available); @@ -1597,9 +1622,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onGesture", - gestureInfo.toString()); + if (svcClientTracingEnabled()) { + logTraceSvcClient("onGesture", gestureInfo.toString()); } listener.onGesture(gestureInfo); } catch (RemoteException re) { @@ -1613,8 +1637,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onSystemActionsChanged"); + if (svcClientTracingEnabled()) { + logTraceSvcClient("onSystemActionsChanged", ""); } listener.onSystemActionsChanged(); } catch (RemoteException re) { @@ -1628,8 +1652,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".clearAccessibilityCache"); + if (svcClientTracingEnabled()) { + logTraceSvcClient("clearAccessibilityCache", ""); } listener.clearAccessibilityCache(); } catch (RemoteException re) { @@ -1747,6 +1771,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ LocalServices.getService(ActivityTaskManagerInternal.class) .setFocusedActivity(activityToken); } + if (intConnTracingEnabled()) { + logTraceIntConn("performAccessibilityAction", + accessibilityNodeId + ";" + action + ";" + arguments + ";" + interactionId + + ";" + callback + ";" + mFetchFlags + ";" + interrogatingPid + ";" + + interrogatingTid); + } connection.getRemote().performAccessibilityAction(accessibilityNodeId, action, arguments, interactionId, callback, fetchFlags, interrogatingPid, interrogatingTid); @@ -1957,8 +1987,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setGestureDetectionPassthroughRegion(int displayId, Region region) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setGestureDetectionPassthroughRegion", + if (svcConnTracingEnabled()) { + logTraceSvcConn("setGestureDetectionPassthroughRegion", "displayId=" + displayId + ";region=" + region); } mSystemSupport.setGestureDetectionPassthroughRegion(displayId, region); @@ -1966,8 +1996,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setTouchExplorationPassthroughRegion(int displayId, Region region) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setTouchExplorationPassthroughRegion", + if (svcConnTracingEnabled()) { + logTraceSvcConn("setTouchExplorationPassthroughRegion", "displayId=" + displayId + ";region=" + region); } mSystemSupport.setTouchExplorationPassthroughRegion(displayId, region); @@ -1975,20 +2005,56 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setFocusAppearance(int strokeWidth, int color) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setFocusAppearance", - "strokeWidth=" + strokeWidth + ";color=" + color); + if (svcConnTracingEnabled()) { + logTraceSvcConn("setFocusAppearance", "strokeWidth=" + strokeWidth + ";color=" + color); } } @Override - public void logTrace(long timestamp, String where, String callingParams, int processId, - long threadId, int callingUid, Bundle callingStack) { - if (mTrace.isA11yTracingEnabled()) { + public void logTrace(long timestamp, String where, long loggingTypes, String callingParams, + int processId, long threadId, int callingUid, Bundle callingStack) { + if (mTrace.isA11yTracingEnabledForTypes(loggingTypes)) { ArrayList<StackTraceElement> list = (ArrayList<StackTraceElement>) callingStack.getSerializable(CALL_STACK); - mTrace.logTrace(timestamp, where, callingParams, processId, threadId, callingUid, - list.toArray(new StackTraceElement[list.size()])); + HashSet<String> ignoreList = + (HashSet<String>) callingStack.getSerializable(IGNORE_CALL_STACK); + mTrace.logTrace(timestamp, where, loggingTypes, callingParams, processId, threadId, + callingUid, list.toArray(new StackTraceElement[list.size()]), ignoreList); } } + + protected boolean svcClientTracingEnabled() { + return mTrace.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT); + } + + protected void logTraceSvcClient(String methodName, String params) { + mTrace.logTrace(TRACE_SVC_CLIENT + "." + methodName, + FLAGS_ACCESSIBILITY_SERVICE_CLIENT, params); + } + + protected boolean svcConnTracingEnabled() { + return mTrace.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CONNECTION); + } + + protected void logTraceSvcConn(String methodName, String params) { + mTrace.logTrace(TRACE_SVC_CONN + "." + methodName, + FLAGS_ACCESSIBILITY_SERVICE_CONNECTION, params); + } + + protected boolean intConnTracingEnabled() { + return mTrace.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION); + } + + protected void logTraceIntConn(String methodName, String params) { + mTrace.logTrace(LOG_TAG + "." + methodName, + FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION, params); + } + + protected boolean wmTracingEnabled() { + return mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL); + } + + protected void logTraceWM(String methodName, String params) { + mTrace.logTrace(TRACE_WM + "." + methodName, FLAGS_WINDOW_MANAGER_INTERNAL, params); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 7403af7605bc..7d2b71f7852b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -19,7 +19,9 @@ package com.android.server.accessibility; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY; +import android.accessibilityservice.AccessibilityTrace; import android.annotation.MainThread; +import android.annotation.NonNull; import android.content.Context; import android.graphics.Region; import android.os.PowerManager; @@ -43,7 +45,10 @@ import com.android.server.accessibility.magnification.WindowMagnificationGesture import com.android.server.accessibility.magnification.WindowMagnificationPromptController; import com.android.server.policy.WindowManagerPolicy; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; +import java.util.StringJoiner; /** * This class is an input filter for implementing accessibility features such @@ -171,9 +176,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private int mEnabledFeatures; - private EventStreamState mMouseStreamState; + private final SparseArray<EventStreamState> mMouseStreamStates = new SparseArray<>(0); - private EventStreamState mTouchScreenStreamState; + private final SparseArray<EventStreamState> mTouchScreenStreamStates = new SparseArray<>(0); private EventStreamState mKeyboardStreamState; @@ -211,10 +216,17 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo super.onUninstalled(); } - void onDisplayChanged() { + void onDisplayAdded(@NonNull Display display) { if (mInstalled) { - disableFeatures(); - enableFeatures(); + resetStreamStateForDisplay(display.getDisplayId()); + enableFeaturesForDisplay(display); + } + } + + void onDisplayRemoved(int displayId) { + if (mInstalled) { + disableFeaturesForDisplay(displayId); + resetStreamStateForDisplay(displayId); } } @@ -224,7 +236,12 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo Slog.d(TAG, "Received event: " + event + ", policyFlags=0x" + Integer.toHexString(policyFlags)); } - + if (mAms.getTraceManager().isA11yTracingEnabledForTypes( + AccessibilityTrace.FLAGS_INPUT_FILTER)) { + mAms.getTraceManager().logTrace(TAG + ".onInputEvent", + AccessibilityTrace.FLAGS_INPUT_FILTER, + "event=" + event + ";policyFlags=" + policyFlags); + } if (mEventHandler.size() == 0) { if (DEBUG) Slog.d(TAG, "No mEventHandler for event " + event); super.onInputEvent(event, policyFlags); @@ -237,16 +254,17 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo return; } - int eventSource = event.getSource(); + final int eventSource = event.getSource(); + final int displayId = event.getDisplayId(); if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) { state.reset(); - clearEventsForAllEventHandlers(eventSource); + clearEventStreamHandler(displayId, eventSource); super.onInputEvent(event, policyFlags); return; } if (state.updateInputSource(event.getSource())) { - clearEventsForAllEventHandlers(eventSource); + clearEventStreamHandler(displayId, eventSource); } if (!state.inputSourceValid()) { @@ -275,35 +293,39 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ private EventStreamState getEventStreamState(InputEvent event) { if (event instanceof MotionEvent) { - if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { - if (mTouchScreenStreamState == null) { - mTouchScreenStreamState = new TouchScreenEventStreamState(); - } - return mTouchScreenStreamState; - } - if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { - if (mMouseStreamState == null) { - mMouseStreamState = new MouseEventStreamState(); - } - return mMouseStreamState; - } + final int displayId = event.getDisplayId(); + + if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { + EventStreamState touchScreenStreamState = mTouchScreenStreamStates.get(displayId); + if (touchScreenStreamState == null) { + touchScreenStreamState = new TouchScreenEventStreamState(); + mTouchScreenStreamStates.put(displayId, touchScreenStreamState); + } + return touchScreenStreamState; + } + if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { + EventStreamState mouseStreamState = mMouseStreamStates.get(displayId); + if (mouseStreamState == null) { + mouseStreamState = new MouseEventStreamState(); + mMouseStreamStates.put(displayId, mouseStreamState); + } + return mouseStreamState; + } } else if (event instanceof KeyEvent) { - if (event.isFromSource(InputDevice.SOURCE_KEYBOARD)) { - if (mKeyboardStreamState == null) { - mKeyboardStreamState = new KeyboardEventStreamState(); - } - return mKeyboardStreamState; - } + if (event.isFromSource(InputDevice.SOURCE_KEYBOARD)) { + if (mKeyboardStreamState == null) { + mKeyboardStreamState = new KeyboardEventStreamState(); + } + return mKeyboardStreamState; + } } return null; } - private void clearEventsForAllEventHandlers(int eventSource) { - for (int i = 0; i < mEventHandler.size(); i++) { - final EventStreamTransformation eventHandler = mEventHandler.valueAt(i); - if (eventHandler != null) { - eventHandler.clearEvents(eventSource); - } + private void clearEventStreamHandler(int displayId, int eventSource) { + final EventStreamTransformation eventHandler = mEventHandler.get(displayId); + if (eventHandler != null) { + eventHandler.clearEvents(eventSource); } } @@ -419,55 +441,69 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private void enableFeatures() { if (DEBUG) Slog.i(TAG, "enableFeatures()"); - resetStreamState(); + resetAllStreamState(); final ArrayList<Display> displaysList = mAms.getValidDisplayList(); - if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) { - mAutoclickController = new AutoclickController(mContext, mUserId); - addFirstEventHandlerForAllDisplays(displaysList, mAutoclickController); + for (int i = displaysList.size() - 1; i >= 0; i--) { + enableFeaturesForDisplay(displaysList.get(i)); } + enableDisplayIndependentFeatures(); + } - for (int i = displaysList.size() - 1; i >= 0; i--) { - final int displayId = displaysList.get(i).getDisplayId(); - final Context displayContext = mContext.createDisplayContext(displaysList.get(i)); + private void enableFeaturesForDisplay(Display display) { + if (DEBUG) { + Slog.i(TAG, "enableFeaturesForDisplay() : display Id = " + display.getDisplayId()); + } - if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) { - TouchExplorer explorer = new TouchExplorer(displayContext, mAms); - if ((mEnabledFeatures & FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0) { - explorer.setServiceHandlesDoubleTap(true); - } - if ((mEnabledFeatures & FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0) { - explorer.setMultiFingerGesturesEnabled(true); - } - if ((mEnabledFeatures & FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0) { - explorer.setTwoFingerPassthroughEnabled(true); - } - if ((mEnabledFeatures & FLAG_SEND_MOTION_EVENTS) != 0) { - explorer.setSendMotionEventsEnabled(true); - } - addFirstEventHandler(displayId, explorer); - mTouchExplorer.put(displayId, explorer); - } + final Context displayContext = mContext.createDisplayContext(display); + final int displayId = display.getDisplayId(); - if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 - || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) - || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) { - final MagnificationGestureHandler magnificationGestureHandler = - createMagnificationGestureHandler(displayId, - displayContext); - addFirstEventHandler(displayId, magnificationGestureHandler); - mMagnificationGestureHandler.put(displayId, magnificationGestureHandler); + if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) { + if (mAutoclickController == null) { + mAutoclickController = new AutoclickController( + mContext, mUserId, mAms.getTraceManager()); } + addFirstEventHandler(displayId, mAutoclickController); + } - if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) { - MotionEventInjector injector = new MotionEventInjector( - mContext.getMainLooper()); - addFirstEventHandler(displayId, injector); - mMotionEventInjectors.put(displayId, injector); + if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) { + TouchExplorer explorer = new TouchExplorer(displayContext, mAms); + if ((mEnabledFeatures & FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0) { + explorer.setServiceHandlesDoubleTap(true); + } + if ((mEnabledFeatures & FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0) { + explorer.setMultiFingerGesturesEnabled(true); + } + if ((mEnabledFeatures & FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0) { + explorer.setTwoFingerPassthroughEnabled(true); } + if ((mEnabledFeatures & FLAG_SEND_MOTION_EVENTS) != 0) { + explorer.setSendMotionEventsEnabled(true); + } + addFirstEventHandler(displayId, explorer); + mTouchExplorer.put(displayId, explorer); + } + + if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 + || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) + || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) { + final MagnificationGestureHandler magnificationGestureHandler = + createMagnificationGestureHandler(displayId, + displayContext); + addFirstEventHandler(displayId, magnificationGestureHandler); + mMagnificationGestureHandler.put(displayId, magnificationGestureHandler); + } + + if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) { + MotionEventInjector injector = new MotionEventInjector( + mContext.getMainLooper(), mAms.getTraceManager()); + addFirstEventHandler(displayId, injector); + mMotionEventInjectors.put(displayId, injector); } + } + private void enableDisplayIndependentFeatures() { if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) { mAms.setMotionEventInjectors(mMotionEventInjectors); } @@ -500,55 +536,57 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo mEventHandler.put(displayId, eventHandler); } - /** - * Adds an event handler to the event handler chain for all displays. The handler is added at - * the beginning of the chain. - * - * @param displayList The list of displays - * @param handler The handler to be added to the event handlers list. - */ - private void addFirstEventHandlerForAllDisplays(ArrayList<Display> displayList, - EventStreamTransformation handler) { - for (int i = 0; i < displayList.size(); i++) { - final int displayId = displayList.get(i).getDisplayId(); - addFirstEventHandler(displayId, handler); + private void disableFeatures() { + final ArrayList<Display> displaysList = mAms.getValidDisplayList(); + + for (int i = displaysList.size() - 1; i >= 0; i--) { + disableFeaturesForDisplay(displaysList.get(i).getDisplayId()); } + mAms.setMotionEventInjectors(null); + disableDisplayIndependentFeatures(); + + resetAllStreamState(); } - private void disableFeatures() { - for (int i = mMotionEventInjectors.size() - 1; i >= 0; i--) { - final MotionEventInjector injector = mMotionEventInjectors.valueAt(i); - if (injector != null) { - injector.onDestroy(); - } + private void disableFeaturesForDisplay(int displayId) { + if (DEBUG) { + Slog.i(TAG, "disableFeaturesForDisplay() : display Id = " + displayId); } - mAms.setMotionEventInjectors(null); - mMotionEventInjectors.clear(); + + final MotionEventInjector injector = mMotionEventInjectors.get(displayId); + if (injector != null) { + injector.onDestroy(); + mMotionEventInjectors.remove(displayId); + } + + final TouchExplorer explorer = mTouchExplorer.get(displayId); + if (explorer != null) { + explorer.onDestroy(); + mTouchExplorer.remove(displayId); + } + + final MagnificationGestureHandler handler = mMagnificationGestureHandler.get(displayId); + if (handler != null) { + handler.onDestroy(); + mMagnificationGestureHandler.remove(displayId); + } + + final EventStreamTransformation eventStreamTransformation = mEventHandler.get(displayId); + if (eventStreamTransformation != null) { + mEventHandler.remove(displayId); + } + } + + private void disableDisplayIndependentFeatures() { if (mAutoclickController != null) { mAutoclickController.onDestroy(); mAutoclickController = null; } - for (int i = mTouchExplorer.size() - 1; i >= 0; i--) { - final TouchExplorer explorer = mTouchExplorer.valueAt(i); - if (explorer != null) { - explorer.onDestroy(); - } - } - mTouchExplorer.clear(); - for (int i = mMagnificationGestureHandler.size() - 1; i >= 0; i--) { - final MagnificationGestureHandler handler = mMagnificationGestureHandler.valueAt(i); - if (handler != null) { - handler.onDestroy(); - } - } - mMagnificationGestureHandler.clear(); + if (mKeyboardInterceptor != null) { mKeyboardInterceptor.onDestroy(); mKeyboardInterceptor = null; } - - mEventHandler.clear(); - resetStreamState(); } private MagnificationGestureHandler createMagnificationGestureHandler( @@ -563,32 +601,46 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo final Context uiContext = displayContext.createWindowContext( TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, null /* options */); magnificationGestureHandler = new WindowMagnificationGestureHandler(uiContext, - mAms.getWindowMagnificationMgr(), mAms.getMagnificationController(), - detectControlGestures, triggerable, + mAms.getWindowMagnificationMgr(), mAms.getTraceManager(), + mAms.getMagnificationController(), detectControlGestures, triggerable, displayId); } else { final Context uiContext = displayContext.createWindowContext( TYPE_MAGNIFICATION_OVERLAY, null /* options */); magnificationGestureHandler = new FullScreenMagnificationGestureHandler(uiContext, - mAms.getFullScreenMagnificationController(), mAms.getMagnificationController(), - detectControlGestures, triggerable, + mAms.getFullScreenMagnificationController(), mAms.getTraceManager(), + mAms.getMagnificationController(), detectControlGestures, triggerable, new WindowMagnificationPromptController(displayContext, mUserId), displayId); } return magnificationGestureHandler; } - void resetStreamState() { - if (mTouchScreenStreamState != null) { - mTouchScreenStreamState.reset(); - } - if (mMouseStreamState != null) { - mMouseStreamState.reset(); + void resetAllStreamState() { + final ArrayList<Display> displaysList = mAms.getValidDisplayList(); + + for (int i = displaysList.size() - 1; i >= 0; i--) { + resetStreamStateForDisplay(displaysList.get(i).getDisplayId()); } + if (mKeyboardStreamState != null) { mKeyboardStreamState.reset(); } } + void resetStreamStateForDisplay(int displayId) { + final EventStreamState touchScreenStreamState = mTouchScreenStreamStates.get(displayId); + if (touchScreenStreamState != null) { + touchScreenStreamState.reset(); + mTouchScreenStreamStates.remove(displayId); + } + + final EventStreamState mouseStreamState = mMouseStreamStates.get(displayId); + if (mouseStreamState != null) { + mouseStreamState.reset(); + mMouseStreamStates.remove(displayId); + } + } + @Override public void onDestroy() { /* ignore */ @@ -839,4 +891,45 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo mTouchExplorer.get(displayId).setTouchExplorationPassthroughRegion(region); } } + + /** + * Dumps all {@link AccessibilityInputFilter}s here. + */ + public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { + if (mEventHandler == null) { + return; + } + pw.append("A11yInputFilter Info : "); + pw.println(); + + final ArrayList<Display> displaysList = mAms.getValidDisplayList(); + for (int i = 0; i < displaysList.size(); i++) { + final int displayId = displaysList.get(i).getDisplayId(); + EventStreamTransformation next = mEventHandler.get(displayId); + if (next != null) { + pw.append("Enabled features of Display ["); + pw.append(Integer.toString(displayId)); + pw.append("] = "); + + final StringJoiner joiner = new StringJoiner(",", "[", "]"); + + while (next != null) { + if (next instanceof MagnificationGestureHandler) { + joiner.add("MagnificationGesture"); + } else if (next instanceof KeyboardInterceptor) { + joiner.add("KeyboardInterceptor"); + } else if (next instanceof TouchExplorer) { + joiner.add("TouchExplorer"); + } else if (next instanceof AutoclickController) { + joiner.add("AutoclickController"); + } else if (next instanceof MotionEventInjector) { + joiner.add("MotionEventInjector"); + } + next = next.getNext(); + } + pw.append(joiner.toString()); + } + pw.println(); + } + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index f63198866b08..04ef10172c88 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -16,6 +16,15 @@ package com.android.server.accessibility; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER_CLIENT; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_FINGERPRINT; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_PACKAGE_BROADCAST_RECEIVER; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCAST_RECEIVER; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL; import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY; @@ -289,8 +298,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext = context; mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); - mTraceManager = new AccessibilityTraceManager( - mWindowManagerService.getAccessibilityController(), this); + mTraceManager = AccessibilityTraceManager.getInstance( + mWindowManagerService.getAccessibilityController(), this, mLock); mMainHandler = new MainHandler(mContext.getMainLooper()); mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); mPackageManager = packageManager; @@ -311,8 +320,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext = context; mPowerManager = context.getSystemService(PowerManager.class); mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); - mTraceManager = new AccessibilityTraceManager( - mWindowManagerService.getAccessibilityController(), this); + mTraceManager = AccessibilityTraceManager.getInstance( + mWindowManagerService.getAccessibilityController(), this, mLock); mMainHandler = new MainHandler(mContext.getMainLooper()); mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); mPackageManager = mContext.getPackageManager(); @@ -324,7 +333,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mSecurityPolicy = new AccessibilitySecurityPolicy(policyWarningUIController, mContext, this); mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler, - mWindowManagerService, this, mSecurityPolicy, this); + mWindowManagerService, this, mSecurityPolicy, this, mTraceManager); mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); mMagnificationController = new MagnificationController(this, mLock, mContext); init(); @@ -339,26 +348,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public int getCurrentUserIdLocked() { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".getCurrentUserIdLocked"); - } return mCurrentUserId; } @Override public boolean isAccessibilityButtonShown() { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".isAccessibilityButtonShown"); - } return mIsAccessibilityButtonShown; } @Override public void onServiceInfoChangedLocked(AccessibilityUserState userState) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace( - LOG_TAG + ".onServiceInfoChangedLocked", "userState=" + userState); - } mSecurityPolicy.onBoundServicesChangedLocked(userState.mUserId, userState.mBoundServices); scheduleNotifyClientsOfServicesStateChangeLocked(userState); @@ -424,8 +423,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub PackageMonitor monitor = new PackageMonitor() { @Override public void onSomePackagesChanged() { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".PM.onSomePackagesChanged"); + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { + mTraceManager.logTrace(LOG_TAG + ".PM.onSomePackagesChanged", + FLAGS_PACKAGE_BROADCAST_RECEIVER); } synchronized (mLock) { @@ -452,8 +452,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // mBindingServices in binderDied() during updating. Remove services from this // package from mBindingServices, and then update the user state to re-bind new // versions of them. - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { mTraceManager.logTrace(LOG_TAG + ".PM.onPackageUpdateFinished", + FLAGS_PACKAGE_BROADCAST_RECEIVER, "packageName=" + packageName + ";uid=" + uid); } synchronized (mLock) { @@ -485,8 +486,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void onPackageRemoved(String packageName, int uid) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { mTraceManager.logTrace(LOG_TAG + ".PM.onPackageRemoved", + FLAGS_PACKAGE_BROADCAST_RECEIVER, "packageName=" + packageName + ";uid=" + uid); } @@ -529,8 +531,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { mTraceManager.logTrace(LOG_TAG + ".PM.onHandleForceStop", + FLAGS_PACKAGE_BROADCAST_RECEIVER, "intent=" + intent + ";packages=" + packages + ";uid=" + uid + ";doit=" + doit); } @@ -580,8 +583,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".BR.onReceive", + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_USER_BROADCAST_RECEIVER)) { + mTraceManager.logTrace(LOG_TAG + ".BR.onReceive", FLAGS_USER_BROADCAST_RECEIVER, "context=" + context + ";intent=" + intent); } @@ -668,8 +671,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public long addClient(IAccessibilityManagerClient callback, int userId) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".addClient", + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { + mTraceManager.logTrace(LOG_TAG + ".addClient", FLAGS_ACCESSIBILITY_MANAGER, "callback=" + callback + ";userId=" + userId); } @@ -739,8 +742,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void sendAccessibilityEvent(AccessibilityEvent event, int userId) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".sendAccessibilityEvent", + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { + mTraceManager.logTrace(LOG_TAG + ".sendAccessibilityEvent", FLAGS_ACCESSIBILITY_MANAGER, "event=" + event + ";userId=" + userId); } boolean dispatchEvent = false; @@ -803,6 +806,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } if (shouldComputeWindows) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL)) { + mTraceManager.logTrace("WindowManagerInternal.computeWindowsForAccessibility", + FLAGS_WINDOW_MANAGER_INTERNAL, "display=" + displayId); + } final WindowManagerInternal wm = LocalServices.getService( WindowManagerInternal.class); wm.computeWindowsForAccessibility(displayId); @@ -835,9 +842,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void registerSystemAction(RemoteAction action, int actionId) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".registerSystemAction", - "action=" + action + ";actionId=" + actionId); + FLAGS_ACCESSIBILITY_MANAGER, "action=" + action + ";actionId=" + actionId); } mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); getSystemActionPerformer().registerSystemAction(actionId, action); @@ -850,8 +857,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void unregisterSystemAction(int actionId) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".unregisterSystemAction", "actionId=" + actionId); + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { + mTraceManager.logTrace(LOG_TAG + ".unregisterSystemAction", + FLAGS_ACCESSIBILITY_MANAGER, "actionId=" + actionId); } mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); getSystemActionPerformer().unregisterSystemAction(actionId); @@ -867,9 +875,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".getInstalledAccessibilityServiceList", - "userId=" + userId); + FLAGS_ACCESSIBILITY_MANAGER, "userId=" + userId); } final int resolvedUserId; @@ -903,8 +911,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".getEnabledAccessibilityServiceList", + FLAGS_ACCESSIBILITY_MANAGER, "feedbackType=" + feedbackType + ";userId=" + userId); } @@ -936,8 +945,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void interrupt(int userId) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".interrupt", "userId=" + userId); + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { + mTraceManager.logTrace(LOG_TAG + ".interrupt", + FLAGS_ACCESSIBILITY_MANAGER, "userId=" + userId); } List<IAccessibilityServiceClient> interfacesToInterrupt; @@ -966,8 +976,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) { try { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".IAccessibilityServiceClient.onInterrupt"); + if (mTraceManager.isA11yTracingEnabledForTypes( + FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) { + mTraceManager.logTrace(LOG_TAG + ".IAccessibilityServiceClient.onInterrupt", + FLAGS_ACCESSIBILITY_SERVICE_CLIENT); } interfacesToInterrupt.get(i).onInterrupt(); } catch (RemoteException re) { @@ -981,8 +993,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken, IAccessibilityInteractionConnection connection, String packageName, int userId) throws RemoteException { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".addAccessibilityInteractionConnection", + FLAGS_ACCESSIBILITY_MANAGER, "windowToken=" + windowToken + "leashToken=" + leashToken + ";connection=" + connection + "; packageName=" + packageName + ";userId=" + userId); } @@ -993,9 +1006,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void removeAccessibilityInteractionConnection(IWindow window) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".removeAccessibilityInteractionConnection", - "window=" + window); + FLAGS_ACCESSIBILITY_MANAGER, "window=" + window); } mA11yWindowManager.removeAccessibilityInteractionConnection(window); } @@ -1003,9 +1016,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void setPictureInPictureActionReplacingConnection( IAccessibilityInteractionConnection connection) throws RemoteException { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".setPictureInPictureActionReplacingConnection", - "connection=" + connection); + FLAGS_ACCESSIBILITY_MANAGER, "connection=" + connection); } mSecurityPolicy.enforceCallingPermission(Manifest.permission.MODIFY_ACCESSIBILITY_DATA, SET_PIP_ACTION_REPLACEMENT); @@ -1017,10 +1030,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub IAccessibilityServiceClient serviceClient, AccessibilityServiceInfo accessibilityServiceInfo, int flags) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".registerUiTestAutomationService", "owner=" + owner - + ";serviceClient=" + serviceClient + ";accessibilityServiceInfo=" - + accessibilityServiceInfo + ";flags=" + flags); + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { + mTraceManager.logTrace(LOG_TAG + ".registerUiTestAutomationService", + FLAGS_ACCESSIBILITY_MANAGER, + "owner=" + owner + ";serviceClient=" + serviceClient + + ";accessibilityServiceInfo=" + accessibilityServiceInfo + ";flags=" + flags); } mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT, @@ -1037,9 +1051,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".unregisterUiTestAutomationService", - "serviceClient=" + serviceClient); + FLAGS_ACCESSIBILITY_MANAGER, "serviceClient=" + serviceClient); } synchronized (mLock) { mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient); @@ -1049,15 +1063,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void temporaryEnableAccessibilityStateUntilKeyguardRemoved( ComponentName service, boolean touchExplorationEnabled) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace( LOG_TAG + ".temporaryEnableAccessibilityStateUntilKeyguardRemoved", + FLAGS_ACCESSIBILITY_MANAGER, "service=" + service + ";touchExplorationEnabled=" + touchExplorationEnabled); } mSecurityPolicy.enforceCallingPermission( Manifest.permission.TEMPORARY_ENABLE_ACCESSIBILITY, TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED); + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL)) { + mTraceManager.logTrace("WindowManagerInternal.isKeyguardLocked", + FLAGS_WINDOW_MANAGER_INTERNAL); + } if (!mWindowManagerService.isKeyguardLocked()) { return; } @@ -1083,9 +1102,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public IBinder getWindowToken(int windowId, int userId) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".getWindowToken", - "windowId=" + windowId + ";userId=" + userId); + FLAGS_ACCESSIBILITY_MANAGER, "windowId=" + windowId + ";userId=" + userId); } mSecurityPolicy.enforceCallingPermission( @@ -1127,8 +1146,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void notifyAccessibilityButtonClicked(int displayId, String targetName) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonClicked", + FLAGS_ACCESSIBILITY_MANAGER, "displayId=" + displayId + ";targetName=" + targetName); } @@ -1157,9 +1177,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void notifyAccessibilityButtonVisibilityChanged(boolean shown) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonVisibilityChanged", - "shown=" + shown); + FLAGS_ACCESSIBILITY_MANAGER, "shown=" + shown); } mSecurityPolicy.enforceCallingOrSelfPermission( @@ -1190,10 +1210,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void onSystemActionsChanged() { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".onSystemActionsChanged"); - } - synchronized (mLock) { AccessibilityUserState state = getCurrentUserStateLocked(); notifySystemActionsChangedLocked(state); @@ -1256,11 +1272,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public @Nullable MotionEventInjector getMotionEventInjectorForDisplayLocked(int displayId) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".getMotionEventInjectorForDisplayLocked", - "displayId=" + displayId); - } - final long endMillis = SystemClock.uptimeMillis() + WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS; MotionEventInjector motionEventInjector = null; while ((mMotionEventInjectors == null) && (SystemClock.uptimeMillis() < endMillis)) { @@ -1323,6 +1334,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub synchronized (mLock) { token = getWindowToken(windowId, mCurrentUserId); } + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL)) { + mTraceManager.logTrace("WindowManagerInternal.getWindowFrame", + FLAGS_WINDOW_MANAGER_INTERNAL, "token=" + token + ";outBounds=" + outBounds); + } mWindowManagerService.getWindowFrame(token, outBounds); if (!outBounds.isEmpty()) { return true; @@ -1471,7 +1486,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private int getClientStateLocked(AccessibilityUserState userState) { return userState.getClientStateLocked( mUiAutomationManager.isUiAutomationRunningLocked(), - mTraceManager.isA11yTracingEnabled()); + mTraceManager.getTraceStateForAccessibilityManagerClientState()); } private InteractionBridge getInteractionBridge() { @@ -1681,6 +1696,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } private void updateRelevantEventsLocked(AccessibilityUserState userState) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) { + mTraceManager.logTrace(LOG_TAG + ".updateRelevantEventsLocked", + FLAGS_ACCESSIBILITY_SERVICE_CLIENT, "userState=" + userState); + } mMainHandler.post(() -> { broadcastToClients(userState, ignoreRemoteException(client -> { int relevantEventTypes; @@ -1830,12 +1849,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void persistComponentNamesToSettingLocked(String settingName, Set<ComponentName> componentNames, int userId) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".persistComponentNamesToSettingLocked", - "settingName=" + settingName + ";componentNames=" + componentNames + ";userId=" - + userId); - } - persistColonDelimitedSetToSettingLocked(settingName, userId, componentNames, componentName -> componentName.flattenToShortString()); } @@ -1960,7 +1973,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) { + void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) { final int clientState = getClientStateLocked(userState); if (userState.getLastSentClientStateLocked() != clientState && (mGlobalClients.getRegisteredCallbackCount() > 0 @@ -1983,6 +1996,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private void sendStateToClients(int clientState, RemoteCallbackList<IAccessibilityManagerClient> clients) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER_CLIENT)) { + mTraceManager.logTrace(LOG_TAG + ".sendStateToClients", + FLAGS_ACCESSIBILITY_MANAGER_CLIENT, "clientState=" + clientState); + } clients.broadcast(ignoreRemoteException( client -> client.setState(clientState))); } @@ -2003,6 +2020,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private void notifyClientsOfServicesStateChange( RemoteCallbackList<IAccessibilityManagerClient> clients, long uiTimeout) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER_CLIENT)) { + mTraceManager.logTrace(LOG_TAG + ".notifyClientsOfServicesStateChange", + FLAGS_ACCESSIBILITY_MANAGER_CLIENT, "uiTimeout=" + uiTimeout); + } clients.broadcast(ignoreRemoteException( client -> client.notifyServicesStateChanged(uiTimeout))); } @@ -2082,6 +2103,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } if (setInputFilter) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL + | FLAGS_INPUT_FILTER)) { + mTraceManager.logTrace("WindowManagerInternal.setInputFilter", + FLAGS_WINDOW_MANAGER_INTERNAL | FLAGS_INPUT_FILTER, + "inputFilter=" + inputFilter); + } mWindowManagerService.setInputFilter(inputFilter); } } @@ -2805,26 +2832,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @GuardedBy("mLock") @Override public MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".getCompatibleMagnificationSpecLocked", - "windowId=" + windowId); - } - IBinder windowToken = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked( mCurrentUserId, windowId); if (windowToken != null) { - return mWindowManagerService.getCompatibleMagnificationSpecForWindow( - windowToken); + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL)) { + mTraceManager.logTrace(LOG_TAG + ".getCompatibleMagnificationSpecForWindow", + FLAGS_WINDOW_MANAGER_INTERNAL, "windowToken=" + windowToken); + } + + return mWindowManagerService.getCompatibleMagnificationSpecForWindow(windowToken); } return null; } @Override public KeyEventDispatcher getKeyEventDispatcher() { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".getKeyEventDispatcher"); - } - if (mKeyEventDispatcher == null) { mKeyEventDispatcher = new KeyEventDispatcher( mMainHandler, MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, mLock, @@ -2837,13 +2859,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @SuppressWarnings("AndroidFrameworkPendingIntentMutability") public PendingIntent getPendingIntentActivity(Context context, int requestCode, Intent intent, int flags) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".getPendingIntentActivity", - "context=" + context + ";requestCode=" + requestCode + ";intent=" + intent - + ";flags=" + flags); - } - - return PendingIntent.getActivity(context, requestCode, intent, flags); } @@ -2858,9 +2873,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void performAccessibilityShortcut(String targetName) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".performAccessibilityShortcut", - "targetName=" + targetName); + FLAGS_ACCESSIBILITY_MANAGER, "targetName=" + targetName); } if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) @@ -3048,9 +3063,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".getAccessibilityShortcutTargets", - "shortcutType=" + shortcutType); + FLAGS_ACCESSIBILITY_MANAGER, "shortcutType=" + shortcutType); } if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY) @@ -3122,11 +3137,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void sendAccessibilityEventForCurrentUserLocked(AccessibilityEvent event) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".sendAccessibilityEventForCurrentUserLocked", - "event=" + event); - } - sendAccessibilityEventLocked(event, mCurrentUserId); } @@ -3148,8 +3158,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public boolean sendFingerprintGesture(int gestureKeyCode) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes( + FLAGS_ACCESSIBILITY_MANAGER | FLAGS_FINGERPRINT)) { mTraceManager.logTrace(LOG_TAG + ".sendFingerprintGesture", + FLAGS_ACCESSIBILITY_MANAGER | FLAGS_FINGERPRINT, "gestureKeyCode=" + gestureKeyCode); } @@ -3174,9 +3186,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public int getAccessibilityWindowId(@Nullable IBinder windowToken) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".getAccessibilityWindowId", - "windowToken=" + windowToken); + FLAGS_ACCESSIBILITY_MANAGER, "windowToken=" + windowToken); } synchronized (mLock) { @@ -3196,8 +3208,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public long getRecommendedTimeoutMillis() { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".getRecommendedTimeoutMillis"); + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { + mTraceManager.logTrace( + LOG_TAG + ".getRecommendedTimeoutMillis", FLAGS_ACCESSIBILITY_MANAGER); } synchronized(mLock) { @@ -3214,8 +3227,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void setWindowMagnificationConnection( IWindowMagnificationConnection connection) throws RemoteException { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes( + FLAGS_ACCESSIBILITY_MANAGER | FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { mTraceManager.logTrace(LOG_TAG + ".setWindowMagnificationConnection", + FLAGS_ACCESSIBILITY_MANAGER | FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "connection=" + connection); } @@ -3249,9 +3264,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) { - if (mTraceManager.isA11yTracingEnabled()) { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".associateEmbeddedHierarchy", - "host=" + host + ";embedded=" + embedded); + FLAGS_ACCESSIBILITY_MANAGER, "host=" + host + ";embedded=" + embedded); } synchronized (mLock) { @@ -3261,8 +3276,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void disassociateEmbeddedHierarchy(@NonNull IBinder token) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".disassociateEmbeddedHierarchy", "token=" + token); + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { + mTraceManager.logTrace(LOG_TAG + ".disassociateEmbeddedHierarchy", + FLAGS_ACCESSIBILITY_MANAGER, "token=" + token); } synchronized (mLock) { @@ -3274,7 +3290,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * Gets the stroke width of the focus rectangle. * @return The stroke width. */ + @Override public int getFocusStrokeWidth() { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { + mTraceManager.logTrace(LOG_TAG + ".getFocusStrokeWidth", FLAGS_ACCESSIBILITY_MANAGER); + } synchronized (mLock) { final AccessibilityUserState userState = getCurrentUserStateLocked(); @@ -3286,7 +3306,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * Gets the color of the focus rectangle. * @return The color. */ + @Override public int getFocusColor() { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { + mTraceManager.logTrace(LOG_TAG + ".getFocusColor", FLAGS_ACCESSIBILITY_MANAGER); + } synchronized (mLock) { final AccessibilityUserState userState = getCurrentUserStateLocked(); @@ -3314,6 +3338,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub pw.println(); } mA11yWindowManager.dump(fd, pw, args); + if (mHasInputFilter && mInputFilter != null) { + mInputFilter.dump(fd, pw, args); + } pw.println("Global client list info:{"); mGlobalClients.dump(pw, " Client list "); pw.println(" Registered clients:{"); @@ -3350,9 +3377,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public FullScreenMagnificationController getFullScreenMagnificationController() { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".getFullScreenMagnificationController"); - } synchronized (mLock) { return mMagnificationController.getFullScreenMagnificationController(); } @@ -3360,11 +3384,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void onClientChangeLocked(boolean serviceInfoChanged) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".onClientChangeLocked", - "serviceInfoChanged=" + serviceInfoChanged); - } - AccessibilityUserState userState = getUserStateLocked(mCurrentUserId); onUserStateChangedLocked(userState); if (serviceInfoChanged) { @@ -3569,7 +3588,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub synchronized (mLock) { mDisplaysList.add(display); if (mInputFilter != null) { - mInputFilter.onDisplayChanged(); + mInputFilter.onDisplayAdded(display); } AccessibilityUserState userState = getCurrentUserStateLocked(); if (displayId != Display.DEFAULT_DISPLAY) { @@ -3591,7 +3610,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return; } if (mInputFilter != null) { - mInputFilter.onDisplayChanged(); + mInputFilter.onDisplayRemoved(displayId); } AccessibilityUserState userState = getCurrentUserStateLocked(); if (displayId != Display.DEFAULT_DISPLAY) { @@ -3891,11 +3910,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void setGestureDetectionPassthroughRegion(int displayId, Region region) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".setGestureDetectionPassthroughRegion", - "displayId=" + displayId + ";region=" + region); - } - mMainHandler.sendMessage( obtainMessage( AccessibilityManagerService::setGestureDetectionPassthroughRegionInternal, @@ -3906,11 +3920,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void setTouchExplorationPassthroughRegion(int displayId, Region region) { - if (mTraceManager.isA11yTracingEnabled()) { - mTraceManager.logTrace(LOG_TAG + ".setTouchExplorationPassthroughRegion", - "displayId=" + displayId + ";region=" + region); - } - mMainHandler.sendMessage( obtainMessage( AccessibilityManagerService::setTouchExplorationPassthroughRegionInternal, @@ -3939,7 +3948,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (userState.mUserId != mCurrentUserId) { return; } - + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) { + mTraceManager.logTrace(LOG_TAG + ".updateFocusAppearanceDataLocked", + FLAGS_ACCESSIBILITY_SERVICE_CLIENT, "userState=" + userState); + } mMainHandler.post(() -> { broadcastToClients(userState, ignoreRemoteException(client -> { client.mCallback.setFocusAppearance(userState.getFocusStrokeWidthLocked(), @@ -3949,7 +3961,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } - AccessibilityTraceManager getTraceManager() { + public AccessibilityTraceManager getTraceManager() { return mTraceManager; } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 7d75b738d818..467cab5fec04 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -20,6 +20,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; import android.app.PendingIntent; import android.content.ComponentName; @@ -53,10 +54,7 @@ import java.util.Set; */ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection { private static final String LOG_TAG = "AccessibilityServiceConnection"; - private static final String TRACE_A11Y_SERVICE_CONNECTION = - LOG_TAG + ".IAccessibilityServiceConnection"; - private static final String TRACE_A11Y_SERVICE_CLIENT = - LOG_TAG + ".IAccessibilityServiceClient"; + /* Holding a weak reference so there isn't a loop of references. AccessibilityUserState keeps lists of bound and binding services. These are freed on user changes, but just in case it @@ -137,8 +135,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public void disableSelf() { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".disableSelf"); + if (svcConnTracingEnabled()) { + logTraceSvcConn("disableSelf", ""); } synchronized (mLock) { AccessibilityUserState userState = mUserStateWeakReference.get(); @@ -218,9 +216,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect return; } try { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".init", this + ", " + mId + ", " - + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); + if (svcClientTracingEnabled()) { + logTraceSvcClient("init", + this + "," + mId + "," + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); } serviceInterface.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); } catch (RemoteException re) { @@ -264,9 +262,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public boolean setSoftKeyboardShowMode(int showMode) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setSoftKeyboardShowMode", - "showMode=" + showMode); + if (svcConnTracingEnabled()) { + logTraceSvcConn("setSoftKeyboardShowMode", "showMode=" + showMode); } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { @@ -280,8 +277,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public int getSoftKeyboardShowMode() { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getSoftKeyboardShowMode"); + if (svcConnTracingEnabled()) { + logTraceSvcConn("getSoftKeyboardShowMode", ""); } final AccessibilityUserState userState = mUserStateWeakReference.get(); return (userState != null) ? userState.getSoftKeyboardShowModeLocked() : 0; @@ -289,9 +286,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public boolean switchToInputMethod(String imeId) { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".switchToInputMethod", - "imeId=" + imeId); + if (svcConnTracingEnabled()) { + logTraceSvcConn("switchToInputMethod", "imeId=" + imeId); } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { @@ -311,8 +307,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public boolean isAccessibilityButtonAvailable() { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".isAccessibilityButtonAvailable"); + if (svcConnTracingEnabled()) { + logTraceSvcConn("isAccessibilityButtonAvailable", ""); } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { @@ -373,9 +369,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } if (serviceInterface != null) { try { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT - + ".onFingerprintCapturingGesturesChanged", String.valueOf(active)); + if (svcClientTracingEnabled()) { + logTraceSvcClient( + "onFingerprintCapturingGesturesChanged", String.valueOf(active)); } mServiceInterface.onFingerprintCapturingGesturesChanged(active); } catch (RemoteException e) { @@ -394,9 +390,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } if (serviceInterface != null) { try { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onFingerprintGesture", - String.valueOf(gesture)); + if (svcClientTracingEnabled()) { + logTraceSvcClient("onFingerprintGesture", String.valueOf(gesture)); } mServiceInterface.onFingerprintGesture(gesture); } catch (RemoteException e) { @@ -410,15 +405,17 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect if (mSecurityPolicy.canPerformGestures(this)) { MotionEventInjector motionEventInjector = mSystemSupport.getMotionEventInjectorForDisplayLocked(displayId); + if (wmTracingEnabled()) { + logTraceWM("isTouchOrFaketouchDevice", ""); + } if (motionEventInjector != null && mWindowManagerService.isTouchOrFaketouchDevice()) { motionEventInjector.injectEvents( gestureSteps.getList(), mServiceInterface, sequence, displayId); } else { try { - if (mTrace.isA11yTracingEnabled()) { - mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onPerformGestureResult", - sequence + ", false"); + if (svcClientTracingEnabled()) { + logTraceSvcClient("onPerformGestureResult", sequence + ", false"); } mServiceInterface.onPerformGestureResult(sequence, false); } catch (RemoteException re) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java index 6396960281b7..8cf5547b05ec 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java @@ -60,7 +60,7 @@ final class AccessibilityShellCommand extends ShellCommand { } case "start-trace": case "stop-trace": - return mService.getTraceManager().onShellCommand(cmd); + return mService.getTraceManager().onShellCommand(cmd, this); } return -1; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java deleted file mode 100644 index 03914138cd42..000000000000 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (C) 2021 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.accessibility; - -/** - * Interface to log accessibility trace. - */ -public interface AccessibilityTrace { - /** - * Whether the trace is enabled. - */ - boolean isA11yTracingEnabled(); - - /** - * Start tracing. - */ - void startTrace(); - - /** - * Stop tracing. - */ - void stopTrace(); - - /** - * Log one trace entry. - * @param where A string to identify this log entry, which can be used to filter/search - * through the tracing file. - */ - void logTrace(String where); - - /** - * Log one trace entry. - * @param where A string to identify this log entry, which can be used to filter/search - * through the tracing file. - * @param callingParams The parameters for the method to be logged. - */ - void logTrace(String where, String callingParams); - - /** - * Log one trace entry. Accessibility services using AccessibilityInteractionClient to - * make screen content related requests use this API to log entry when receive callback. - * @param timestamp The timestamp when a callback is received. - * @param where A string to identify this log entry, which can be used to filter/search - * through the tracing file. - * @param callingParams The parameters for the callback. - * @param processId The process id of the calling component. - * @param threadId The threadId of the calling component. - * @param callingUid The calling uid of the callback. - * @param callStack The call stack of the callback. - */ - void logTrace(long timestamp, String where, String callingParams, int processId, - long threadId, int callingUid, StackTraceElement[] callStack); -} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java index 6105e8a6724b..51e01ea58a35 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java @@ -15,72 +15,197 @@ */ package com.android.server.accessibility; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CLIENT; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER_CLIENT_STATES; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_LOGGING_ALL; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_LOGGING_NONE; +import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED; +import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED; +import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED; +import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED; + +import android.accessibilityservice.AccessibilityTrace; +import android.annotation.MainThread; import android.annotation.NonNull; import android.os.Binder; +import android.os.ShellCommand; import com.android.server.wm.WindowManagerInternal; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Manager of accessibility trace. */ -class AccessibilityTraceManager implements AccessibilityTrace { +public class AccessibilityTraceManager implements AccessibilityTrace { private final WindowManagerInternal.AccessibilityControllerInternal mA11yController; private final AccessibilityManagerService mService; + private final Object mA11yMSLock; + + private long mEnabledLoggingFlags; + + private static AccessibilityTraceManager sInstance = null; + + @MainThread + static AccessibilityTraceManager getInstance( + @NonNull WindowManagerInternal.AccessibilityControllerInternal a11yController, + @NonNull AccessibilityManagerService service, + @NonNull Object lock) { + if (sInstance == null) { + sInstance = new AccessibilityTraceManager(a11yController, service, lock); + } + return sInstance; + } - AccessibilityTraceManager( + private AccessibilityTraceManager( @NonNull WindowManagerInternal.AccessibilityControllerInternal a11yController, - @NonNull AccessibilityManagerService service) { + @NonNull AccessibilityManagerService service, + @NonNull Object lock) { mA11yController = a11yController; mService = service; + mA11yMSLock = lock; + mEnabledLoggingFlags = FLAGS_LOGGING_NONE; } @Override public boolean isA11yTracingEnabled() { - return mA11yController.isAccessibilityTracingEnabled(); + synchronized (mA11yMSLock) { + return mEnabledLoggingFlags != FLAGS_LOGGING_NONE; + } + } + + @Override + public boolean isA11yTracingEnabledForTypes(long typeIdFlags) { + synchronized (mA11yMSLock) { + return ((typeIdFlags & mEnabledLoggingFlags) != FLAGS_LOGGING_NONE); + } + } + + @Override + public int getTraceStateForAccessibilityManagerClientState() { + int state = 0x0; + synchronized (mA11yMSLock) { + if (isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION)) { + state |= STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED; + } + if (isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK)) { + state |= STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED; + } + if (isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_INTERACTION_CLIENT)) { + state |= STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED; + } + if (isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE)) { + state |= STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED; + } + } + return state; } @Override - public void startTrace() { - if (!mA11yController.isAccessibilityTracingEnabled()) { - mA11yController.startTrace(); - mService.scheduleUpdateClientsIfNeeded(mService.getCurrentUserState()); + public void startTrace(long loggingTypes) { + if (loggingTypes == FLAGS_LOGGING_NONE) { + // Ignore start none request + return; + } + + synchronized (mA11yMSLock) { + long oldEnabled = mEnabledLoggingFlags; + mEnabledLoggingFlags = loggingTypes; + + if (needToNotifyClients(oldEnabled)) { + mService.scheduleUpdateClientsIfNeededLocked(mService.getCurrentUserState()); + } } + + mA11yController.startTrace(loggingTypes); } @Override public void stopTrace() { - if (mA11yController.isAccessibilityTracingEnabled()) { + boolean stop = false; + synchronized (mA11yMSLock) { + stop = isA11yTracingEnabled(); + + long oldEnabled = mEnabledLoggingFlags; + mEnabledLoggingFlags = FLAGS_LOGGING_NONE; + + if (needToNotifyClients(oldEnabled)) { + mService.scheduleUpdateClientsIfNeededLocked(mService.getCurrentUserState()); + } + } + if (stop) { mA11yController.stopTrace(); - mService.scheduleUpdateClientsIfNeeded(mService.getCurrentUserState()); } } @Override - public void logTrace(String where) { - logTrace(where, ""); + public void logTrace(String where, long loggingTypes) { + logTrace(where, loggingTypes, ""); } @Override - public void logTrace(String where, String callingParams) { - mA11yController.logTrace(where, callingParams, "".getBytes(), - Binder.getCallingUid(), Thread.currentThread().getStackTrace()); + public void logTrace(String where, long loggingTypes, String callingParams) { + if (isA11yTracingEnabledForTypes(loggingTypes)) { + mA11yController.logTrace(where, loggingTypes, callingParams, "".getBytes(), + Binder.getCallingUid(), Thread.currentThread().getStackTrace(), + new HashSet<String>(Arrays.asList("logTrace"))); + } } @Override - public void logTrace(long timestamp, String where, String callingParams, int processId, - long threadId, int callingUid, StackTraceElement[] callStack) { - if (mA11yController.isAccessibilityTracingEnabled()) { - mA11yController.logTrace(where, callingParams, "".getBytes(), callingUid, callStack, - timestamp, processId, threadId); + public void logTrace(long timestamp, String where, long loggingTypes, String callingParams, + int processId, long threadId, int callingUid, StackTraceElement[] callStack, + Set<String> ignoreElementList) { + if (isA11yTracingEnabledForTypes(loggingTypes)) { + mA11yController.logTrace(where, loggingTypes, callingParams, "".getBytes(), callingUid, + callStack, timestamp, processId, threadId, + ((ignoreElementList == null) ? new HashSet<String>() : ignoreElementList)); } } - int onShellCommand(String cmd) { + private boolean needToNotifyClients(long otherTypesEnabled) { + return (mEnabledLoggingFlags & FLAGS_ACCESSIBILITY_MANAGER_CLIENT_STATES) + != (otherTypesEnabled & FLAGS_ACCESSIBILITY_MANAGER_CLIENT_STATES); + } + + int onShellCommand(String cmd, ShellCommand shell) { switch (cmd) { case "start-trace": { - startTrace(); + String opt = shell.getNextOption(); + if (opt == null) { + startTrace(FLAGS_LOGGING_ALL); + return 0; + } + List<String> types = new ArrayList<String>(); + while (opt != null) { + switch (opt) { + case "-t": { + String type = shell.getNextArg(); + while (type != null) { + types.add(type); + type = shell.getNextArg(); + } + break; + } + default: { + shell.getErrPrintWriter().println( + "Error: option not recognized " + opt); + stopTrace(); + return -1; + } + } + opt = shell.getNextOption(); + } + long enabledTypes = AccessibilityTrace.getLoggingFlagsFromNames(types); + startTrace(enabledTypes); return 0; } case "stop-trace": { @@ -92,8 +217,29 @@ class AccessibilityTraceManager implements AccessibilityTrace { } void onHelp(PrintWriter pw) { - pw.println(" start-trace"); - pw.println(" Start the debug tracing."); + pw.println(" start-trace [-t LOGGING_TYPE [LOGGING_TYPE...]]"); + pw.println(" Start the debug tracing. If no option is present, full trace will be"); + pw.println(" generated. Options are:"); + pw.println(" -t: Only generate tracing for the logging type(s) specified here."); + pw.println(" LOGGING_TYPE can be any one of below:"); + pw.println(" IAccessibilityServiceConnection"); + pw.println(" IAccessibilityServiceClient"); + pw.println(" IAccessibilityManager"); + pw.println(" IAccessibilityManagerClient"); + pw.println(" IAccessibilityInteractionConnection"); + pw.println(" IAccessibilityInteractionConnectionCallback"); + pw.println(" IRemoteMagnificationAnimationCallback"); + pw.println(" IWindowMagnificationConnection"); + pw.println(" IWindowMagnificationConnectionCallback"); + pw.println(" WindowManagerInternal"); + pw.println(" WindowsForAccessibilityCallback"); + pw.println(" MagnificationCallbacks"); + pw.println(" InputFilter"); + pw.println(" Gesture"); + pw.println(" AccessibilityService"); + pw.println(" PMBroadcastReceiver"); + pw.println(" UserBroadcastReceiver"); + pw.println(" FingerprintGesture"); pw.println(" stop-trace"); pw.println(" Stop the debug tracing."); } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index 0fde0de59c07..c70bf73fc7f5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -392,7 +392,7 @@ class AccessibilityUserState { return mBoundServices; } - int getClientStateLocked(boolean isUiAutomationRunning, boolean isTracingEnabled) { + int getClientStateLocked(boolean isUiAutomationRunning, int traceClientState) { int clientState = 0; final boolean a11yEnabled = isUiAutomationRunning || isHandlingAccessibilityEventsLocked(); @@ -408,9 +408,9 @@ class AccessibilityUserState { if (mIsTextHighContrastEnabled) { clientState |= AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED; } - if (isTracingEnabled) { - clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_TRACING_ENABLED; - } + + clientState |= traceClientState; + return clientState; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index ff794691d2b4..244f35700d8b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -16,6 +16,8 @@ package com.android.server.accessibility; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED; @@ -69,6 +71,7 @@ public class AccessibilityWindowManager { private final AccessibilityEventSender mAccessibilityEventSender; private final AccessibilitySecurityPolicy mSecurityPolicy; private final AccessibilityUserManager mAccessibilityUserManager; + private final AccessibilityTraceManager mTraceManager; // Connections and window tokens for cross-user windows private final SparseArray<RemoteAccessibilityConnection> @@ -151,6 +154,10 @@ public class AccessibilityWindowManager { // In some cases, onWindowsForAccessibilityChanged will be called immediately in // setWindowsForAccessibilityCallback. We'll lost windows if flag is false. mTrackingWindows = true; + if (traceWMEnabled()) { + logTraceWM("setWindowsForAccessibilityCallback", + "displayId=" + mDisplayId + ";callback=" + this); + } result = mWindowManagerInternal.setWindowsForAccessibilityCallback( mDisplayId, this); if (!result) { @@ -167,6 +174,10 @@ public class AccessibilityWindowManager { */ void stopTrackingWindowsLocked() { if (mTrackingWindows) { + if (traceWMEnabled()) { + logTraceWM("setWindowsForAccessibilityCallback", + "displayId=" + mDisplayId + ";callback=null"); + } mWindowManagerInternal.setWindowsForAccessibilityCallback( mDisplayId, null); mTrackingWindows = false; @@ -373,6 +384,20 @@ public class AccessibilityWindowManager { } } + /** + * Called when the display is reparented and becomes an embedded + * display. + * + * @param embeddedDisplayId The embedded display Id. + */ + @Override + public void onDisplayReparented(int embeddedDisplayId) { + // Removes the un-used window observer for the embedded display. + synchronized (mLock) { + mDisplayWindowsObservers.remove(embeddedDisplayId); + } + } + private boolean shouldUpdateWindowsLocked(boolean forceSend, @NonNull List<WindowInfo> windows) { if (forceSend) { @@ -474,6 +499,9 @@ public class AccessibilityWindowManager { if (oldWindow.displayId != newWindow.displayId) { return true; } + if (oldWindow.taskId != newWindow.taskId) { + return true; + } return false; } @@ -674,6 +702,7 @@ public class AccessibilityWindowManager { reportedWindow.setAnchorId(window.accessibilityIdOfAnchor); reportedWindow.setPictureInPicture(window.inPictureInPicture); reportedWindow.setDisplayId(window.displayId); + reportedWindow.setTaskId(window.taskId); final int parentId = findWindowIdLocked(userId, window.parentToken); if (parentId >= 0) { @@ -844,19 +873,21 @@ public class AccessibilityWindowManager { } /** - * Constructor for AccessibilityManagerService. + * Constructor for AccessibilityWindowManager. */ public AccessibilityWindowManager(@NonNull Object lock, @NonNull Handler handler, @NonNull WindowManagerInternal windowManagerInternal, @NonNull AccessibilityEventSender accessibilityEventSender, @NonNull AccessibilitySecurityPolicy securityPolicy, - @NonNull AccessibilityUserManager accessibilityUserManager) { + @NonNull AccessibilityUserManager accessibilityUserManager, + @NonNull AccessibilityTraceManager traceManager) { mLock = lock; mHandler = handler; mWindowManagerInternal = windowManagerInternal; mAccessibilityEventSender = accessibilityEventSender; mSecurityPolicy = securityPolicy; mAccessibilityUserManager = accessibilityUserManager; + mTraceManager = traceManager; } /** @@ -957,6 +988,9 @@ public class AccessibilityWindowManager { final int windowId; boolean shouldComputeWindows = false; final IBinder token = window.asBinder(); + if (traceWMEnabled()) { + logTraceWM("getDisplayIdForWindow", "token=" + token); + } final int displayId = mWindowManagerInternal.getDisplayIdForWindow(token); synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles @@ -1003,9 +1037,15 @@ public class AccessibilityWindowManager { registerIdLocked(leashToken, windowId); } if (shouldComputeWindows) { + if (traceWMEnabled()) { + logTraceWM("computeWindowsForAccessibility", "displayId=" + displayId); + } mWindowManagerInternal.computeWindowsForAccessibility(displayId); } - + if (traceWMEnabled()) { + logTraceWM("setAccessibilityIdToSurfaceMetadata", + "token=" + token + ";windowId=" + windowId); + } mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata(token, windowId); return windowId; } @@ -1139,6 +1179,10 @@ public class AccessibilityWindowManager { mActiveWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; } if (binder != null) { + if (traceWMEnabled()) { + logTraceWM("setAccessibilityIdToSurfaceMetadata", "token=" + binder + + ";windowId=AccessibilityWindowInfo.UNDEFINED_WINDOW_ID"); + } mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata( binder, AccessibilityWindowInfo.UNDEFINED_WINDOW_ID); } @@ -1169,6 +1213,9 @@ public class AccessibilityWindowManager { * @return The userId */ public int getWindowOwnerUserId(@NonNull IBinder windowToken) { + if (traceWMEnabled()) { + logTraceWM("getWindowOwnerUserId", "token=" + windowToken); + } return mWindowManagerInternal.getWindowOwnerUserId(windowToken); } @@ -1547,6 +1594,10 @@ public class AccessibilityWindowManager { for (int i = 0; i < connectionList.size(); i++) { final RemoteAccessibilityConnection connection = connectionList.get(i); if (connection != null) { + if (traceIntConnEnabled()) { + logTraceIntConn("notifyOutsideTouch"); + } + try { connection.getRemote().notifyOutsideTouch(); } catch (RemoteException re) { @@ -1567,6 +1618,9 @@ public class AccessibilityWindowManager { */ public int getDisplayIdByUserIdAndWindowIdLocked(int userId, int windowId) { final IBinder windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId); + if (traceWMEnabled()) { + logTraceWM("getDisplayIdForWindow", "token=" + windowToken); + } final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken); return displayId; } @@ -1595,6 +1649,9 @@ public class AccessibilityWindowManager { * @return The input focused windowId, or -1 if not found */ private int findFocusedWindowId(int userId) { + if (traceWMEnabled()) { + logTraceWM("getFocusedWindowToken", ""); + } final IBinder token = mWindowManagerInternal.getFocusedWindowToken(); synchronized (mLock) { return findWindowIdLocked(userId, token); @@ -1644,6 +1701,9 @@ public class AccessibilityWindowManager { return; } } + if (traceIntConnEnabled()) { + logTraceIntConn("notifyOutsideTouch"); + } try { connection.getRemote().clearAccessibilityFocus(); } catch (RemoteException re) { @@ -1666,6 +1726,25 @@ public class AccessibilityWindowManager { return null; } + private boolean traceWMEnabled() { + return mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL); + } + + private void logTraceWM(String methodName, String params) { + mTraceManager.logTrace("WindowManagerInternal." + methodName, + FLAGS_WINDOW_MANAGER_INTERNAL, params); + } + + private boolean traceIntConnEnabled() { + return mTraceManager.isA11yTracingEnabledForTypes( + FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION); + } + + private void logTraceIntConn(String methodName) { + mTraceManager.logTrace( + LOG_TAG + "." + methodName, FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION); + } + /** * Associate the token of the embedded view hierarchy to the host view hierarchy. * diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java index f5b0eb1eb5b6..95f3560dbbb8 100644 --- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java @@ -16,6 +16,7 @@ package com.android.server.accessibility; +import android.accessibilityservice.AccessibilityTrace; import android.annotation.NonNull; import android.content.ContentResolver; import android.content.Context; @@ -56,6 +57,7 @@ public class AutoclickController extends BaseEventStreamTransformation { private static final String LOG_TAG = AutoclickController.class.getSimpleName(); + private final AccessibilityTraceManager mTrace; private final Context mContext; private final int mUserId; @@ -63,13 +65,18 @@ public class AutoclickController extends BaseEventStreamTransformation { private ClickScheduler mClickScheduler; private ClickDelayObserver mClickDelayObserver; - public AutoclickController(Context context, int userId) { + public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) { + mTrace = trace; mContext = context; mUserId = userId; } @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mTrace.isA11yTracingEnabledForTypes(AccessibilityTrace.FLAGS_INPUT_FILTER)) { + mTrace.logTrace(LOG_TAG + ".onMotionEvent", AccessibilityTrace.FLAGS_INPUT_FILTER, + "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags); + } if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { if (mClickScheduler == null) { Handler handler = new Handler(mContext.getMainLooper()); @@ -89,6 +96,10 @@ public class AutoclickController extends BaseEventStreamTransformation { @Override public void onKeyEvent(KeyEvent event, int policyFlags) { + if (mTrace.isA11yTracingEnabledForTypes(AccessibilityTrace.FLAGS_INPUT_FILTER)) { + mTrace.logTrace(LOG_TAG + ".onKeyEvent", AccessibilityTrace.FLAGS_INPUT_FILTER, + "event=" + event + ";policyFlags=" + policyFlags); + } if (mClickScheduler != null) { if (KeyEvent.isModifierKey(event.getKeyCode())) { mClickScheduler.updateMetaState(event.getMetaState()); diff --git a/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java b/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java index bc379c204d44..b8250c028f62 100644 --- a/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java +++ b/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java @@ -16,6 +16,8 @@ package com.android.server.accessibility; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER; + import android.os.Handler; import android.os.Message; import android.os.SystemClock; @@ -64,6 +66,10 @@ public class KeyboardInterceptor extends BaseEventStreamTransformation implement @Override public void onKeyEvent(KeyEvent event, int policyFlags) { + if (mAms.getTraceManager().isA11yTracingEnabledForTypes(FLAGS_INPUT_FILTER)) { + mAms.getTraceManager().logTrace(LOG_TAG + ".onKeyEvent", + FLAGS_INPUT_FILTER, "event=" + event + ";policyFlags=" + policyFlags); + } /* * Certain keys have system-level behavior that affects accessibility services. * Let that behavior settle before handling the keys diff --git a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java index 2673cd1ac3b8..5cbd1a208ce1 100644 --- a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java +++ b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java @@ -16,6 +16,7 @@ package com.android.server.accessibility; +import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.GestureDescription; import android.accessibilityservice.GestureDescription.GestureStep; import android.accessibilityservice.GestureDescription.TouchPoint; @@ -68,6 +69,7 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement private final Handler mHandler; private final SparseArray<Boolean> mOpenGesturesInProgress = new SparseArray<>(); + private final AccessibilityTraceManager mTrace; private IAccessibilityServiceClient mServiceInterfaceForCurrentGesture; private IntArray mSequencesInProgress = new IntArray(5); private boolean mIsDestroyed = false; @@ -80,15 +82,17 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement /** * @param looper A looper on the main thread to use for dispatching new events */ - public MotionEventInjector(Looper looper) { + public MotionEventInjector(Looper looper, AccessibilityTraceManager trace) { mHandler = new Handler(looper, this); + mTrace = trace; } /** * @param handler A handler to post messages. Exposes internal state for testing only. */ - public MotionEventInjector(Handler handler) { + public MotionEventInjector(Handler handler, AccessibilityTraceManager trace) { mHandler = handler; + mTrace = trace; } /** @@ -112,6 +116,12 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mTrace.isA11yTracingEnabledForTypes( + AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE)) { + mTrace.logTrace(LOG_TAG + ".onMotionEvent", + AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE, + "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags); + } // MotionEventInjector would cancel any injected gesture when any MotionEvent arrives. // For user using an external device to control the pointer movement, it's almost // impossible to perform the gestures. Any slightly unintended movement results in the diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java index eaf269415fdc..6744ea8e26a5 100644 --- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java +++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java @@ -288,8 +288,6 @@ public class SystemActionPerformer { showGlobalActions(); return true; } - case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: - return toggleSplitScreen(); case AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN: return lockScreen(); case AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT: @@ -369,21 +367,6 @@ public class SystemActionPerformer { mWindowManagerService.showGlobalActions(); } - private boolean toggleSplitScreen() { - final long token = Binder.clearCallingIdentity(); - try { - StatusBarManagerInternal statusBarService = LocalServices.getService( - StatusBarManagerInternal.class); - if (statusBarService == null) { - return false; - } - statusBarService.toggleSplitScreen(); - } finally { - Binder.restoreCallingIdentity(token); - } - return true; - } - private boolean lockScreen() { mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0); diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 9547280018e4..6cd23fcfd0fb 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -17,6 +17,7 @@ package com.android.server.accessibility; import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; import android.annotation.Nullable; import android.app.UiAutomation; @@ -269,6 +270,14 @@ class UiAutomationManager { // If the serviceInterface is null, the UiAutomation has been shut down on // another thread. if (serviceInterface != null) { + if (mTrace.isA11yTracingEnabledForTypes( + AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) { + mTrace.logTrace("UiAutomationService.connectServiceUnknownThread", + AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT, + "serviceConnection=" + this + ";connectionId=" + mId + + "windowToken=" + + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); + } serviceInterface.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index d7bc04091181..74f0bcb4245c 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -16,6 +16,8 @@ package com.android.server.accessibility.gestures; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_GESTURE; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_HOVER_ENTER; @@ -82,6 +84,7 @@ public class TouchExplorer extends BaseEventStreamTransformation implements GestureManifold.Listener { static final boolean DEBUG = false; + private static final long LOGGING_FLAGS = FLAGS_GESTURE | FLAGS_INPUT_FILTER; // Tag for logging received events. private static final String LOG_TAG = "TouchExplorer"; @@ -254,6 +257,10 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) { + mAms.getTraceManager().logTrace(LOG_TAG + ".onMotionEvent", LOGGING_FLAGS, + "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags); + } if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { super.onMotionEvent(event, rawEvent, policyFlags); return; @@ -308,6 +315,10 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public void onAccessibilityEvent(AccessibilityEvent event) { + if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) { + mAms.getTraceManager().logTrace(LOG_TAG + ".onAccessibilityEvent", + LOGGING_FLAGS, "event=" + event); + } final int eventType = event.getEventType(); if (eventType == TYPE_VIEW_HOVER_EXIT) { @@ -346,6 +357,10 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) { + mAms.getTraceManager().logTrace(LOG_TAG + ".onDoubleTapAndHold", LOGGING_FLAGS, + "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags); + } if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) { sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); if (isSendMotionEventsEnabled()) { @@ -362,6 +377,10 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public boolean onDoubleTap(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) { + mAms.getTraceManager().logTrace(LOG_TAG + ".onDoubleTap", LOGGING_FLAGS, + "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags); + } mAms.onTouchInteractionEnd(); // Remove pending event deliveries. mSendHoverEnterAndMoveDelayed.cancel(); @@ -394,6 +413,9 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public boolean onGestureStarted() { + if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) { + mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureStarted", LOGGING_FLAGS); + } // We have to perform gesture detection, so // clear the current state and try to detect. mSendHoverEnterAndMoveDelayed.cancel(); @@ -407,6 +429,10 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent) { + if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) { + mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureCompleted", + LOGGING_FLAGS, "event=" + gestureEvent); + } endGestureDetection(true); mSendTouchInteractionEndDelayed.cancel(); dispatchGesture(gestureEvent); @@ -415,6 +441,10 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) { + mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureCancelled", LOGGING_FLAGS, + "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags); + } if (mState.isGestureDetecting()) { endGestureDetection(event.getActionMasked() == ACTION_UP); return true; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index 1f66bfdb20c1..718da9e390ab 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -16,6 +16,8 @@ package com.android.server.accessibility.magnification; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL; + import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.NonNull; @@ -46,6 +48,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityManagerService; +import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.wm.WindowManagerInternal; import java.util.Locale; @@ -135,6 +138,10 @@ public class FullScreenMagnificationController { */ @GuardedBy("mLock") boolean register() { + if (traceEnabled()) { + logTrace("setMagnificationCallbacks", + "displayID=" + mDisplayId + ";callback=" + this); + } mRegistered = mControllerCtx.getWindowManager().setMagnificationCallbacks( mDisplayId, this); if (!mRegistered) { @@ -142,6 +149,10 @@ public class FullScreenMagnificationController { return false; } mSpecAnimationBridge.setEnabled(true); + if (traceEnabled()) { + logTrace("getMagnificationRegion", + "displayID=" + mDisplayId + ";region=" + mMagnificationRegion); + } // Obtain initial state. mControllerCtx.getWindowManager().getMagnificationRegion( mDisplayId, mMagnificationRegion); @@ -162,6 +173,10 @@ public class FullScreenMagnificationController { void unregister(boolean delete) { if (mRegistered) { mSpecAnimationBridge.setEnabled(false); + if (traceEnabled()) { + logTrace("setMagnificationCallbacks", + "displayID=" + mDisplayId + ";callback=null"); + } mControllerCtx.getWindowManager().setMagnificationCallbacks( mDisplayId, null); mMagnificationRegion.setEmpty(); @@ -268,7 +283,7 @@ public class FullScreenMagnificationController { } @Override - public void onRotationChanged(int rotation) { + public void onDisplaySizeChanged() { // Treat as context change and reset final Message m = PooledLambda.obtainMessage( FullScreenMagnificationController::resetIfNeeded, @@ -431,6 +446,10 @@ public class FullScreenMagnificationController { void setForceShowMagnifiableBounds(boolean show) { if (mRegistered) { mForceShowMagnifiableBounds = show; + if (traceEnabled()) { + logTrace("setForceShowMagnifiableBounds", + "displayID=" + mDisplayId + ";show=" + show); + } mControllerCtx.getWindowManager().setForceShowMagnifiableBounds( mDisplayId, show); } @@ -1255,6 +1274,16 @@ public class FullScreenMagnificationController { } } + private boolean traceEnabled() { + return mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes( + FLAGS_WINDOW_MANAGER_INTERNAL); + } + + private void logTrace(String methodName, String params) { + mControllerCtx.getTraceManager().logTrace( + "WindowManagerInternal." + methodName, FLAGS_WINDOW_MANAGER_INTERNAL, params); + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -1320,6 +1349,13 @@ public class FullScreenMagnificationController { mEnabled = enabled; if (!mEnabled) { mSentMagnificationSpec.clear(); + if (mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes( + FLAGS_WINDOW_MANAGER_INTERNAL)) { + mControllerCtx.getTraceManager().logTrace( + "WindowManagerInternal.setMagnificationSpec", + FLAGS_WINDOW_MANAGER_INTERNAL, + "displayID=" + mDisplayId + ";spec=" + mSentMagnificationSpec); + } mControllerCtx.getWindowManager().setMagnificationSpec( mDisplayId, mSentMagnificationSpec); } @@ -1367,6 +1403,13 @@ public class FullScreenMagnificationController { } mSentMagnificationSpec.setTo(spec); + if (mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes( + FLAGS_WINDOW_MANAGER_INTERNAL)) { + mControllerCtx.getTraceManager().logTrace( + "WindowManagerInternal.setMagnificationSpec", + FLAGS_WINDOW_MANAGER_INTERNAL, + "displayID=" + mDisplayId + ";spec=" + mSentMagnificationSpec); + } mControllerCtx.getWindowManager().setMagnificationSpec( mDisplayId, mSentMagnificationSpec); } @@ -1455,6 +1498,7 @@ public class FullScreenMagnificationController { public static class ControllerContext { private final Context mContext; private final AccessibilityManagerService mAms; + private final AccessibilityTraceManager mTrace; private final WindowManagerInternal mWindowManager; private final Handler mHandler; private final Long mAnimationDuration; @@ -1469,6 +1513,7 @@ public class FullScreenMagnificationController { long animationDuration) { mContext = context; mAms = ams; + mTrace = ams.getTraceManager(); mWindowManager = windowManager; mHandler = handler; mAnimationDuration = animationDuration; @@ -1491,6 +1536,14 @@ public class FullScreenMagnificationController { } /** + * @return AccessibilityTraceManager + */ + @NonNull + public AccessibilityTraceManager getTraceManager() { + return mTrace; + } + + /** * @return WindowManagerInternal */ @NonNull diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index f7d1b9a311ba..c3d8d4c2c96a 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -61,6 +61,7 @@ import android.view.ViewConfiguration; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.server.accessibility.AccessibilityManagerService; +import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.gestures.GestureUtils; /** @@ -142,12 +143,13 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH public FullScreenMagnificationGestureHandler(@UiContext Context context, FullScreenMagnificationController fullScreenMagnificationController, + AccessibilityTraceManager trace, Callback callback, boolean detectTripleTap, boolean detectShortcutTrigger, @NonNull WindowMagnificationPromptController promptController, int displayId) { - super(displayId, detectTripleTap, detectShortcutTrigger, callback); + super(displayId, detectTripleTap, detectShortcutTrigger, trace, callback); if (DEBUG_ALL) { Log.i(mLogTag, "FullScreenMagnificationGestureHandler(detectTripleTap = " + detectTripleTap diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index f9aecd739d33..8aacafbd05c7 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -411,8 +411,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb synchronized (mLock) { if (mWindowMagnificationMgr == null) { mWindowMagnificationMgr = new WindowMagnificationManager(mContext, - mAms.getCurrentUserIdLocked(), - this); + mAms.getCurrentUserIdLocked(), this, mAms.getTraceManager()); } return mWindowMagnificationMgr; } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java index bbe40b6faf74..19b339645557 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java @@ -20,11 +20,13 @@ import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_UP; +import android.accessibilityservice.AccessibilityTrace; import android.annotation.NonNull; import android.util.Log; import android.util.Slog; import android.view.MotionEvent; +import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.BaseEventStreamTransformation; import java.util.ArrayDeque; @@ -99,14 +101,17 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo void onTripleTapped(int displayId, int mode); } + private final AccessibilityTraceManager mTrace; protected final Callback mCallback; protected MagnificationGestureHandler(int displayId, boolean detectTripleTap, boolean detectShortcutTrigger, + AccessibilityTraceManager trace, @NonNull Callback callback) { mDisplayId = displayId; mDetectTripleTap = detectTripleTap; mDetectShortcutTrigger = detectShortcutTrigger; + mTrace = trace; mCallback = callback; mDebugInputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null; @@ -118,6 +123,12 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo if (DEBUG_ALL) { Slog.i(mLogTag, "onMotionEvent(" + event + ")"); } + if (mTrace.isA11yTracingEnabledForTypes( + AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE)) { + mTrace.logTrace("MagnificationGestureHandler.onMotionEvent", + AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE, + "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags); + } if (DEBUG_EVENT_STREAM) { storeEventInto(mDebugInputEventHistory, event); } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java index 993027d1ca3c..527742536480 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java @@ -16,6 +16,9 @@ package com.android.server.accessibility.magnification; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK; import static android.os.IBinder.DeathRecipient; import android.annotation.NonNull; @@ -27,6 +30,8 @@ import android.view.accessibility.IWindowMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; import android.view.accessibility.MagnificationAnimationCallback; +import com.android.server.accessibility.AccessibilityTraceManager; + /** * A wrapper of {@link IWindowMagnificationConnection}. */ @@ -36,9 +41,12 @@ class WindowMagnificationConnectionWrapper { private static final String TAG = "WindowMagnificationConnectionWrapper"; private final @NonNull IWindowMagnificationConnection mConnection; + private final @NonNull AccessibilityTraceManager mTrace; - WindowMagnificationConnectionWrapper(@NonNull IWindowMagnificationConnection connection) { + WindowMagnificationConnectionWrapper(@NonNull IWindowMagnificationConnection connection, + @NonNull AccessibilityTraceManager trace) { mConnection = connection; + mTrace = trace; } //Should not use this instance anymore after calling it. @@ -52,9 +60,15 @@ class WindowMagnificationConnectionWrapper { boolean enableWindowMagnification(int displayId, float scale, float centerX, float centerY, @Nullable MagnificationAnimationCallback callback) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + mTrace.logTrace(TAG + ".enableWindowMagnification", + FLAGS_WINDOW_MAGNIFICATION_CONNECTION, + "displayId=" + displayId + ";scale=" + scale + ";centerX=" + centerX + + ";centerY=" + centerY + ";callback=" + callback); + } try { mConnection.enableWindowMagnification(displayId, scale, centerX, centerY, - transformToRemoteCallback(callback)); + transformToRemoteCallback(callback, mTrace)); } catch (RemoteException e) { if (DBG) { Slog.e(TAG, "Error calling enableWindowMagnification()", e); @@ -65,6 +79,10 @@ class WindowMagnificationConnectionWrapper { } boolean setScale(int displayId, float scale) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + mTrace.logTrace(TAG + ".setScale", FLAGS_WINDOW_MAGNIFICATION_CONNECTION, + "displayId=" + displayId + ";scale=" + scale); + } try { mConnection.setScale(displayId, scale); } catch (RemoteException e) { @@ -78,8 +96,14 @@ class WindowMagnificationConnectionWrapper { boolean disableWindowMagnification(int displayId, @Nullable MagnificationAnimationCallback callback) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + mTrace.logTrace(TAG + ".disableWindowMagnification", + FLAGS_WINDOW_MAGNIFICATION_CONNECTION, + "displayId=" + displayId + ";callback=" + callback); + } try { - mConnection.disableWindowMagnification(displayId, transformToRemoteCallback(callback)); + mConnection.disableWindowMagnification(displayId, + transformToRemoteCallback(callback, mTrace)); } catch (RemoteException e) { if (DBG) { Slog.e(TAG, "Error calling disableWindowMagnification()", e); @@ -90,6 +114,10 @@ class WindowMagnificationConnectionWrapper { } boolean moveWindowMagnifier(int displayId, float offsetX, float offsetY) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + mTrace.logTrace(TAG + ".moveWindowMagnifier", FLAGS_WINDOW_MAGNIFICATION_CONNECTION, + "displayId=" + displayId + ";offsetX=" + offsetX + ";offsetY=" + offsetY); + } try { mConnection.moveWindowMagnifier(displayId, offsetX, offsetY); } catch (RemoteException e) { @@ -102,6 +130,11 @@ class WindowMagnificationConnectionWrapper { } boolean showMagnificationButton(int displayId, int magnificationMode) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + mTrace.logTrace(TAG + ".showMagnificationButton", + FLAGS_WINDOW_MAGNIFICATION_CONNECTION, + "displayId=" + displayId + ";mode=" + magnificationMode); + } try { mConnection.showMagnificationButton(displayId, magnificationMode); } catch (RemoteException e) { @@ -114,6 +147,10 @@ class WindowMagnificationConnectionWrapper { } boolean removeMagnificationButton(int displayId) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + mTrace.logTrace(TAG + ".removeMagnificationButton", + FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId); + } try { mConnection.removeMagnificationButton(displayId); } catch (RemoteException e) { @@ -126,6 +163,14 @@ class WindowMagnificationConnectionWrapper { } boolean setConnectionCallback(IWindowMagnificationConnectionCallback connectionCallback) { + if (mTrace.isA11yTracingEnabledForTypes( + FLAGS_WINDOW_MAGNIFICATION_CONNECTION + | FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { + mTrace.logTrace(TAG + ".setConnectionCallback", + FLAGS_WINDOW_MAGNIFICATION_CONNECTION + | FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + "callback=" + connectionCallback); + } try { mConnection.setConnectionCallback(connectionCallback); } catch (RemoteException e) { @@ -139,25 +184,38 @@ class WindowMagnificationConnectionWrapper { private static @Nullable IRemoteMagnificationAnimationCallback transformToRemoteCallback( - MagnificationAnimationCallback callback) { + MagnificationAnimationCallback callback, AccessibilityTraceManager trace) { if (callback == null) { return null; } - return new RemoteAnimationCallback(callback); + return new RemoteAnimationCallback(callback, trace); } private static class RemoteAnimationCallback extends IRemoteMagnificationAnimationCallback.Stub { - private final MagnificationAnimationCallback mCallback; + private final AccessibilityTraceManager mTrace; - RemoteAnimationCallback(@NonNull MagnificationAnimationCallback callback) { + RemoteAnimationCallback(@NonNull MagnificationAnimationCallback callback, + @NonNull AccessibilityTraceManager trace) { mCallback = callback; + mTrace = trace; + if (mTrace.isA11yTracingEnabledForTypes( + FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK)) { + mTrace.logTrace("RemoteAnimationCallback.constructor", + FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK, "callback=" + callback); + } } @Override public void onResult(boolean success) throws RemoteException { mCallback.onResult(success); + if (mTrace.isA11yTracingEnabledForTypes( + FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK)) { + mTrace.logTrace("RemoteAnimationCallback.onResult", + FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK, "success=" + success); + } + } } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java index 4fb9a03b8ac1..b26d36493a3e 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -34,6 +34,7 @@ import android.view.Display; import android.view.MotionEvent; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.EventStreamTransformation; import com.android.server.accessibility.gestures.MultiTap; import com.android.server.accessibility.gestures.MultiTapAndHold; @@ -89,9 +90,10 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl public WindowMagnificationGestureHandler(@UiContext Context context, WindowMagnificationManager windowMagnificationMgr, + AccessibilityTraceManager trace, Callback callback, boolean detectTripleTap, boolean detectShortcutTrigger, int displayId) { - super(displayId, detectTripleTap, detectShortcutTrigger, callback); + super(displayId, detectTripleTap, detectShortcutTrigger, trace, callback); if (DEBUG_ALL) { Slog.i(mLogTag, "WindowMagnificationGestureHandler() , displayId = " + displayId + ")"); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index 938cb73dac79..7a111d80b42e 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -16,6 +16,9 @@ package com.android.server.accessibility.magnification; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.BroadcastReceiver; @@ -39,6 +42,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; +import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.statusbar.StatusBarManagerInternal; /** @@ -111,11 +115,14 @@ public class WindowMagnificationManager implements } private final Callback mCallback; + private final AccessibilityTraceManager mTrace; - public WindowMagnificationManager(Context context, int userId, @NonNull Callback callback) { + public WindowMagnificationManager(Context context, int userId, @NonNull Callback callback, + AccessibilityTraceManager trace) { mContext = context; mUserId = userId; mCallback = callback; + mTrace = trace; } /** @@ -135,7 +142,7 @@ public class WindowMagnificationManager implements mConnectionWrapper = null; } if (connection != null) { - mConnectionWrapper = new WindowMagnificationConnectionWrapper(connection); + mConnectionWrapper = new WindowMagnificationConnectionWrapper(connection, mTrace); } if (mConnectionWrapper != null) { @@ -197,7 +204,10 @@ public class WindowMagnificationManager implements } } } - + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + mTrace.logTrace(TAG + ".requestWindowMagnificationConnection", + FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "connect=" + connect); + } final long identity = Binder.clearCallingIdentity(); try { final StatusBarManagerInternal service = LocalServices.getService( @@ -511,6 +521,12 @@ public class WindowMagnificationManager implements @Override public void onWindowMagnifierBoundsChanged(int displayId, Rect bounds) { + if (mTrace.isA11yTracingEnabledForTypes( + FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { + mTrace.logTrace(TAG + "ConnectionCallback.onWindowMagnifierBoundsChanged", + FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + "displayId=" + displayId + ";bounds=" + bounds); + } synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); if (magnifier == null) { @@ -527,11 +543,23 @@ public class WindowMagnificationManager implements @Override public void onChangeMagnificationMode(int displayId, int magnificationMode) throws RemoteException { + if (mTrace.isA11yTracingEnabledForTypes( + FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { + mTrace.logTrace(TAG + "ConnectionCallback.onChangeMagnificationMode", + FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + "displayId=" + displayId + ";mode=" + magnificationMode); + } //TODO: Uses this method to change the magnification mode on non-default display. } @Override public void onSourceBoundsChanged(int displayId, Rect sourceBounds) { + if (mTrace.isA11yTracingEnabledForTypes( + FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { + mTrace.logTrace(TAG + "ConnectionCallback.onSourceBoundsChanged", + FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + "displayId=" + displayId + ";source=" + sourceBounds); + } synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); if (magnifier == null) { @@ -543,11 +571,23 @@ public class WindowMagnificationManager implements @Override public void onPerformScaleAction(int displayId, float scale) { + if (mTrace.isA11yTracingEnabledForTypes( + FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { + mTrace.logTrace(TAG + "ConnectionCallback.onPerformScaleAction", + FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + "displayId=" + displayId + ";scale=" + scale); + } mCallback.onPerformScaleAction(displayId, scale); } @Override public void onAccessibilityActionPerformed(int displayId) { + if (mTrace.isA11yTracingEnabledForTypes( + FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { + mTrace.logTrace(TAG + "ConnectionCallback.onAccessibilityActionPerformed", + FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + "displayId=" + displayId); + } mCallback.onAccessibilityActionPerformed(displayId); } diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java index 4946ad442b0a..1af8ad344190 100644 --- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java @@ -187,7 +187,7 @@ public class AppPredictionPerUserService extends @NonNull IPredictionCallback callback) { final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); if (sessionInfo == null) return; - final boolean serviceExists = resolveService(sessionId, false, + final boolean serviceExists = resolveService(sessionId, true, sessionInfo.mUsesPeopleService, s -> s.registerPredictionUpdates(sessionId, callback)); if (serviceExists) { diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 9ee0159e903a..0b95fefec103 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -4068,14 +4068,20 @@ public class UserBackupManagerService { } int operationType; + TransportClient transportClient = null; try { - operationType = getOperationTypeFromTransport( - mTransportManager.getTransportClientOrThrow(transport, /* caller */ - "BMS.beginRestoreSession")); + transportClient = mTransportManager.getTransportClientOrThrow( + transport, /* caller */"BMS.beginRestoreSession"); + operationType = getOperationTypeFromTransport(transportClient); } catch (TransportNotAvailableException | TransportNotRegisteredException | RemoteException e) { Slog.w(TAG, "Failed to get operation type from transport: " + e); return null; + } finally { + if (transportClient != null) { + mTransportManager.disposeOfTransportClient(transportClient, + /* caller */"BMS.beginRestoreSession"); + } } synchronized (this) { diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 30de4b416410..7c1e2da4d6a3 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -457,6 +457,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind }, FgThread.getExecutor()).whenComplete(uncheckExceptions((association, err) -> { if (err == null) { addAssociation(association, userId); + mServiceConnectors.forUser(userId).post(service -> { + service.onAssociationCreated(); + }); } else { Slog.e(LOG_TAG, "Failed to discover device(s)", err); callback.onFailure("No devices found: " + err.getMessage()); @@ -566,7 +569,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind return PendingIntent.getActivityAsUser(getContext(), 0 /* request code */, NotificationAccessConfirmationActivityContract.launcherIntent( - userId, component, packageTitle), + getContext(), userId, component, packageTitle), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT, null /* options */, @@ -1448,8 +1451,12 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } void restartBleScan() { - mBluetoothAdapter.getBluetoothLeScanner().stopScan(mBleScanCallback); - startBleScan(); + if (mBluetoothAdapter.getBluetoothLeScanner() != null) { + mBluetoothAdapter.getBluetoothLeScanner().stopScan(mBleScanCallback); + startBleScan(); + } else { + Slog.w(LOG_TAG, "BluetoothLeScanner is null (likely BLE isn't ON yet)."); + } } private List<ScanFilter> getBleScanFilters() { diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 757f1aeb275f..61d784ecb5f3 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -223,7 +223,7 @@ public final class ContentCaptureManagerService extends @Override // from AbstractMasterSystemService protected ContentCapturePerUserService newServiceLocked(@UserIdInt int resolvedUserId, boolean disabled) { - return new ContentCapturePerUserService(this, mLock, disabled, resolvedUserId); + return new ContentCapturePerUserService(this, mLock, disabled, resolvedUserId, mHandler); } @Override // from SystemService diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 904def0af2cf..822a42bf50cf 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -46,6 +46,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.os.UserHandle; import android.provider.Settings; @@ -75,6 +76,7 @@ import com.android.server.contentcapture.RemoteContentCaptureService.ContentCapt import com.android.server.infra.AbstractPerUserSystemService; import java.io.PrintWriter; +import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -88,6 +90,10 @@ final class ContentCapturePerUserService private static final String TAG = ContentCapturePerUserService.class.getSimpleName(); + private static final int MAX_REBIND_COUNTS = 5; + // 5 minutes + private static final long REBIND_DURATION_MS = 5 * 60 * 1_000; + @GuardedBy("mLock") private final SparseArray<ContentCaptureServerSession> mSessions = new SparseArray<>(); @@ -121,11 +127,18 @@ final class ContentCapturePerUserService @GuardedBy("mLock") private ContentCaptureServiceInfo mInfo; + private Instant mLastRebindTime; + private int mRebindCount; + private final Handler mHandler; + + private final Runnable mReBindServiceRunnable = new RebindServiceRunnable(); + // TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's ContentCapturePerUserService(@NonNull ContentCaptureManagerService master, - @NonNull Object lock, boolean disabled, @UserIdInt int userId) { + @NonNull Object lock, boolean disabled, @UserIdInt int userId, Handler handler) { super(master, lock, userId); + mHandler = handler; updateRemoteServiceLocked(disabled); } @@ -190,6 +203,43 @@ final class ContentCapturePerUserService Slog.w(TAG, "remote service died: " + service); synchronized (mLock) { mZombie = true; + // Reset rebindCount if over 12 hours mLastRebindTime + if (mLastRebindTime != null && Instant.now().isAfter( + mLastRebindTime.plusMillis(12 * 60 * 60 * 1000))) { + if (mMaster.debug) { + Slog.i(TAG, "The current rebind count " + mRebindCount + " is reset."); + } + mRebindCount = 0; + } + if (mRebindCount >= MAX_REBIND_COUNTS) { + writeServiceEvent( + FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_REMOTE_SERVICE_DIED, + getServiceComponentName()); + } + if (mRebindCount < MAX_REBIND_COUNTS) { + mHandler.removeCallbacks(mReBindServiceRunnable); + mHandler.postDelayed(mReBindServiceRunnable, REBIND_DURATION_MS); + } + } + } + + private void updateRemoteServiceAndResurrectSessionsLocked() { + boolean disabled = !isEnabledLocked(); + updateRemoteServiceLocked(disabled); + resurrectSessionsLocked(); + } + + private final class RebindServiceRunnable implements Runnable{ + + @Override + public void run() { + synchronized (mLock) { + if (mZombie) { + mLastRebindTime = Instant.now(); + mRebindCount++; + updateRemoteServiceAndResurrectSessionsLocked(); + } + } } } @@ -237,8 +287,8 @@ final class ContentCapturePerUserService } void onPackageUpdatedLocked() { - updateRemoteServiceLocked(!isEnabledLocked()); - resurrectSessionsLocked(); + mRebindCount = 0; + updateRemoteServiceAndResurrectSessionsLocked(); } @GuardedBy("mLock") @@ -552,6 +602,8 @@ final class ContentCapturePerUserService mInfo.dump(prefix2, pw); } pw.print(prefix); pw.print("Zombie: "); pw.println(mZombie); + pw.print(prefix); pw.print("Rebind count: "); pw.println(mRebindCount); + pw.print(prefix); pw.print("Last rebind: "); pw.println(mLastRebindTime); if (mRemoteService != null) { pw.print(prefix); pw.println("remote service:"); diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java index 9f3045e68550..3a90a959b877 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java @@ -87,7 +87,7 @@ final class ContentCaptureServerSession { mId = sessionId; mUid = uid; mContentCaptureContext = new ContentCaptureContext(/* clientContext= */ null, - activityId, appComponentName, displayId, flags); + activityId, appComponentName, displayId, activityToken, flags); mSessionStateReceiver = sessionStateReceiver; try { sessionStateReceiver.asBinder().linkToDeath(() -> onClientDeath(), 0); diff --git a/services/core/Android.bp b/services/core/Android.bp index dab95a3cc764..914637caff81 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -152,7 +152,7 @@ java_library_static { "android.hardware.biometrics.fingerprint-V2.3-java", "android.hardware.biometrics.fingerprint-V1-java", "android.hardware.oemlock-V1.0-java", - "android.hardware.configstore-V1.0-java", + "android.hardware.configstore-V1.1-java", "android.hardware.contexthub-V1.0-java", "android.hardware.rebootescrow-V1-java", "android.hardware.soundtrigger-V2.3-java", diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 6f25e8d1acd8..806ea95424a7 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -110,10 +110,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private static final String BLUETOOTH_PRIVILEGED = android.Manifest.permission.BLUETOOTH_PRIVILEGED; - private static final String SECURE_SETTINGS_BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid"; - private static final String SECURE_SETTINGS_BLUETOOTH_ADDRESS = "bluetooth_address"; - private static final String SECURE_SETTINGS_BLUETOOTH_NAME = "bluetooth_name"; - private static final int ACTIVE_LOG_MAX_SIZE = 20; private static final int CRASH_LOG_MAX_SIZE = 100; @@ -643,7 +639,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (mContext.getResources() .getBoolean(com.android.internal.R.bool.config_bluetooth_address_validation) && Settings.Secure.getIntForUser(mContentResolver, - SECURE_SETTINGS_BLUETOOTH_ADDR_VALID, 0, mUserId) + Settings.Secure.BLUETOOTH_NAME, 0, mUserId) == 0) { // if the valid flag is not set, don't load the address and name if (DBG) { @@ -652,9 +648,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return; } mName = Settings.Secure.getStringForUser( - mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME, mUserId); + mContentResolver, Settings.Secure.BLUETOOTH_NAME, mUserId); mAddress = Settings.Secure.getStringForUser( - mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS, mUserId); + mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, mUserId); if (DBG) { Slog.d(TAG, "Stored bluetooth Name=" + mName + ",Address=" + mAddress); } @@ -668,30 +664,30 @@ class BluetoothManagerService extends IBluetoothManager.Stub { */ private void storeNameAndAddress(String name, String address) { if (name != null) { - Settings.Secure.putStringForUser(mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME, name, + Settings.Secure.putStringForUser(mContentResolver, Settings.Secure.BLUETOOTH_NAME, name, mUserId); mName = name; if (DBG) { Slog.d(TAG, "Stored Bluetooth name: " + Settings.Secure.getStringForUser( - mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME, + mContentResolver, Settings.Secure.BLUETOOTH_NAME, mUserId)); } } if (address != null) { - Settings.Secure.putStringForUser(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS, + Settings.Secure.putStringForUser(mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, address, mUserId); mAddress = address; if (DBG) { Slog.d(TAG, "Stored Bluetoothaddress: " + Settings.Secure.getStringForUser( - mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS, + mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, mUserId)); } } if ((name != null) && (address != null)) { - Settings.Secure.putIntForUser(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDR_VALID, 1, + Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.BLUETOOTH_ADDR_VALID, 1, mUserId); } } @@ -2080,6 +2076,10 @@ class BluetoothManagerService extends IBluetoothManager.Stub { mEnable = true; mQuietEnable = (quietEnable == 1); + if (isBle == 0) { + persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); + } + // Use service interface to get the exact state try { mBluetoothLock.readLock().lock(); @@ -2093,7 +2093,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } else { Slog.w(TAG, "BT Enable in BLE_ON State, going to ON"); mBluetooth.onLeServiceUp(mContext.getAttributionSource()); - persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); } break; case BluetoothAdapter.STATE_BLE_TURNING_ON: diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 4e5e458d1636..ad1eeb45763f 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -47,6 +47,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; import android.telecom.TelecomManager; +import android.telephony.AccessNetworkConstants; import android.telephony.Annotation; import android.telephony.Annotation.RadioPowerState; import android.telephony.Annotation.SrvccState; @@ -1025,7 +1026,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { return; } - int phoneId = getPhoneIdFromSubId(subId); synchronized (mRecords) { // register IBinder b = callback.asBinder(); @@ -1048,21 +1048,24 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID, // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID if (!SubscriptionManager.isValidSubscriptionId(subId)) { + if (DBG) { + log("invalid subscription id, use default id"); + } r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; } else {//APP specify subID r.subId = subId; } - r.phoneId = phoneId; + r.phoneId = getPhoneIdFromSubId(r.subId); r.eventList = events; if (DBG) { - log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId); + log("listen: Register r=" + r + " r.subId=" + r.subId + " r.phoneId=" + r.phoneId); } - if (notifyNow && validatePhoneId(phoneId)) { + if (notifyNow && validatePhoneId(r.phoneId)) { if (events.contains(TelephonyCallback.EVENT_SERVICE_STATE_CHANGED)){ try { - if (VDBG) log("listen: call onSSC state=" + mServiceState[phoneId]); - ServiceState rawSs = new ServiceState(mServiceState[phoneId]); + if (VDBG) log("listen: call onSSC state=" + mServiceState[r.phoneId]); + ServiceState rawSs = new ServiceState(mServiceState[r.phoneId]); if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { r.callback.onServiceStateChanged(rawSs); } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) { @@ -1078,8 +1081,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } if (events.contains(TelephonyCallback.EVENT_SIGNAL_STRENGTH_CHANGED)) { try { - if (mSignalStrength[phoneId] != null) { - int gsmSignalStrength = mSignalStrength[phoneId] + if (mSignalStrength[r.phoneId] != null) { + int gsmSignalStrength = mSignalStrength[r.phoneId] .getGsmSignalStrength(); r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1 : gsmSignalStrength)); @@ -1092,7 +1095,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { TelephonyCallback.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)) { try { r.callback.onMessageWaitingIndicatorChanged( - mMessageWaiting[phoneId]); + mMessageWaiting[r.phoneId]); } catch (RemoteException ex) { remove(r.binder); } @@ -1101,7 +1104,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { TelephonyCallback.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)) { try { r.callback.onCallForwardingIndicatorChanged( - mCallForwarding[phoneId]); + mCallForwarding[r.phoneId]); } catch (RemoteException ex) { remove(r.binder); } @@ -1109,11 +1112,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (validateEventAndUserLocked( r, TelephonyCallback.EVENT_CELL_LOCATION_CHANGED)) { try { - if (DBG_LOC) log("listen: mCellIdentity = " + mCellIdentity[phoneId]); + if (DBG_LOC) log("listen: mCellIdentity = " + mCellIdentity[r.phoneId]); if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE) && checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { // null will be translated to empty CellLocation object in client. - r.callback.onCellLocationChanged(mCellIdentity[phoneId]); + r.callback.onCellLocationChanged(mCellIdentity[r.phoneId]); } } catch (RemoteException ex) { remove(r.binder); @@ -1121,38 +1124,38 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } if (events.contains(TelephonyCallback.EVENT_LEGACY_CALL_STATE_CHANGED)) { try { - r.callback.onLegacyCallStateChanged(mCallState[phoneId], - getCallIncomingNumber(r, phoneId)); + r.callback.onLegacyCallStateChanged(mCallState[r.phoneId], + getCallIncomingNumber(r, r.phoneId)); } catch (RemoteException ex) { remove(r.binder); } } if (events.contains(TelephonyCallback.EVENT_CALL_STATE_CHANGED)) { try { - r.callback.onCallStateChanged(mCallState[phoneId]); + r.callback.onCallStateChanged(mCallState[r.phoneId]); } catch (RemoteException ex) { remove(r.binder); } } if (events.contains(TelephonyCallback.EVENT_DATA_CONNECTION_STATE_CHANGED)) { try { - r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId], - mDataConnectionNetworkType[phoneId]); + r.callback.onDataConnectionStateChanged(mDataConnectionState[r.phoneId], + mDataConnectionNetworkType[r.phoneId]); } catch (RemoteException ex) { remove(r.binder); } } if (events.contains(TelephonyCallback.EVENT_DATA_ACTIVITY_CHANGED)) { try { - r.callback.onDataActivity(mDataActivity[phoneId]); + r.callback.onDataActivity(mDataActivity[r.phoneId]); } catch (RemoteException ex) { remove(r.binder); } } if (events.contains(TelephonyCallback.EVENT_SIGNAL_STRENGTHS_CHANGED)) { try { - if (mSignalStrength[phoneId] != null) { - r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]); + if (mSignalStrength[r.phoneId] != null) { + r.callback.onSignalStrengthsChanged(mSignalStrength[r.phoneId]); } } catch (RemoteException ex) { remove(r.binder); @@ -1162,8 +1165,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { TelephonyCallback.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) { updateReportSignalStrengthDecision(r.subId); try { - if (mSignalStrength[phoneId] != null) { - r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]); + if (mSignalStrength[r.phoneId] != null) { + r.callback.onSignalStrengthsChanged(mSignalStrength[r.phoneId]); } } catch (RemoteException ex) { remove(r.binder); @@ -1172,11 +1175,13 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (validateEventAndUserLocked( r, TelephonyCallback.EVENT_CELL_INFO_CHANGED)) { try { - if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = " - + mCellInfo.get(phoneId)); + if (DBG_LOC) { + log("listen: mCellInfo[" + r.phoneId + "] = " + + mCellInfo.get(r.phoneId)); + } if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE) && checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { - r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); + r.callback.onCellInfoChanged(mCellInfo.get(r.phoneId)); } } catch (RemoteException ex) { remove(r.binder); @@ -1184,22 +1189,22 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } if (events.contains(TelephonyCallback.EVENT_PRECISE_CALL_STATE_CHANGED)) { try { - r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]); + r.callback.onPreciseCallStateChanged(mPreciseCallState[r.phoneId]); } catch (RemoteException ex) { remove(r.binder); } } if (events.contains(TelephonyCallback.EVENT_CALL_DISCONNECT_CAUSE_CHANGED)) { try { - r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause[phoneId], - mCallPreciseDisconnectCause[phoneId]); + r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause[r.phoneId], + mCallPreciseDisconnectCause[r.phoneId]); } catch (RemoteException ex) { remove(r.binder); } } if (events.contains(TelephonyCallback.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED)) { try { - r.callback.onImsCallDisconnectCauseChanged(mImsReasonInfo.get(phoneId)); + r.callback.onImsCallDisconnectCauseChanged(mImsReasonInfo.get(r.phoneId)); } catch (RemoteException ex) { remove(r.binder); } @@ -1208,7 +1213,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { TelephonyCallback.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED)) { try { for (PreciseDataConnectionState pdcs - : mPreciseDataConnectionStates.get(phoneId).values()) { + : mPreciseDataConnectionStates.get(r.phoneId).values()) { r.callback.onPreciseDataConnectionStateChanged(pdcs); } } catch (RemoteException ex) { @@ -1225,29 +1230,29 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (events.contains(TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED)) { try { r.callback.onVoiceActivationStateChanged( - mVoiceActivationState[phoneId]); + mVoiceActivationState[r.phoneId]); } catch (RemoteException ex) { remove(r.binder); } } if (events.contains(TelephonyCallback.EVENT_DATA_ACTIVATION_STATE_CHANGED)) { try { - r.callback.onDataActivationStateChanged(mDataActivationState[phoneId]); + r.callback.onDataActivationStateChanged(mDataActivationState[r.phoneId]); } catch (RemoteException ex) { remove(r.binder); } } if (events.contains(TelephonyCallback.EVENT_USER_MOBILE_DATA_STATE_CHANGED)) { try { - r.callback.onUserMobileDataStateChanged(mUserMobileDataState[phoneId]); + r.callback.onUserMobileDataStateChanged(mUserMobileDataState[r.phoneId]); } catch (RemoteException ex) { remove(r.binder); } } if (events.contains(TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED)) { try { - if (mTelephonyDisplayInfos[phoneId] != null) { - r.callback.onDisplayInfoChanged(mTelephonyDisplayInfos[phoneId]); + if (mTelephonyDisplayInfos[r.phoneId] != null) { + r.callback.onDisplayInfoChanged(mTelephonyDisplayInfos[r.phoneId]); } } catch (RemoteException ex) { remove(r.binder); @@ -1284,20 +1289,20 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } if (events.contains(TelephonyCallback.EVENT_SRVCC_STATE_CHANGED)) { try { - r.callback.onSrvccStateChanged(mSrvccState[phoneId]); + r.callback.onSrvccStateChanged(mSrvccState[r.phoneId]); } catch (RemoteException ex) { remove(r.binder); } } if (events.contains(TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)) { try { - r.callback.onCallAttributesChanged(mCallAttributes[phoneId]); + r.callback.onCallAttributesChanged(mCallAttributes[r.phoneId]); } catch (RemoteException ex) { remove(r.binder); } } if (events.contains(TelephonyCallback.EVENT_BARRING_INFO_CHANGED)) { - BarringInfo barringInfo = mBarringInfo.get(phoneId); + BarringInfo barringInfo = mBarringInfo.get(r.phoneId); BarringInfo biNoLocation = barringInfo != null ? barringInfo.createLocationInfoSanitizedCopy() : null; if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo); @@ -1315,8 +1320,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { r.callback.onPhysicalChannelConfigChanged( shouldSanitizeLocationForPhysicalChannelConfig(r) ? getLocationSanitizedConfigs( - mPhysicalChannelConfigs.get(phoneId)) - : mPhysicalChannelConfigs.get(phoneId)); + mPhysicalChannelConfigs.get(r.phoneId)) + : mPhysicalChannelConfigs.get(r.phoneId)); } catch (RemoteException ex) { remove(r.binder); } @@ -1325,7 +1330,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { TelephonyCallback.EVENT_DATA_ENABLED_CHANGED)) { try { r.callback.onDataEnabledChanged( - mIsDataEnabled[phoneId], mDataEnabledReason[phoneId]); + mIsDataEnabled[r.phoneId], mDataEnabledReason[r.phoneId]); } catch (RemoteException ex) { remove(r.binder); } @@ -1333,9 +1338,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (events.contains( TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED)) { try { - if (mLinkCapacityEstimateLists.get(phoneId) != null) { + if (mLinkCapacityEstimateLists.get(r.phoneId) != null) { r.callback.onLinkCapacityEstimateChanged(mLinkCapacityEstimateLists - .get(phoneId)); + .get(r.phoneId)); } } catch (RemoteException ex) { remove(r.binder); @@ -1577,7 +1582,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_SERVICE_STATE_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { ServiceState stateToSend; @@ -1639,7 +1644,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if ((activationType == SIM_ACTIVATION_TYPE_VOICE) && r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { if (DBG) { log("notifyVoiceActivationStateForPhoneId: callback.onVASC r=" + r + " subId=" + subId + " phoneId=" + phoneId @@ -1650,7 +1655,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if ((activationType == SIM_ACTIVATION_TYPE_DATA) && r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_DATA_ACTIVATION_STATE_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { if (DBG) { log("notifyDataActivationStateForPhoneId: callback.onDASC r=" + r + " subId=" + subId + " phoneId=" + phoneId @@ -1692,7 +1697,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { TelephonyCallback.EVENT_SIGNAL_STRENGTHS_CHANGED) || r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { if (DBG) { log("notifySignalStrengthForPhoneId: callback.onSsS r=" + r @@ -1706,7 +1711,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_SIGNAL_STRENGTH_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { int gsmSignalStrength = signalStrength.getGsmSignalStrength(); int ss = (gsmSignalStrength == 99 ? -1 : gsmSignalStrength); @@ -1753,7 +1758,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onCarrierNetworkChange(active); } catch (RemoteException ex) { @@ -1785,7 +1790,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (validateEventAndUserLocked( r, TelephonyCallback.EVENT_CELL_INFO_CHANGED) - && idMatch(r.subId, subId, phoneId) + && idMatch(r, subId, phoneId) && (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE) && checkFineLocationAccess(r, Build.VERSION_CODES.Q))) { try { @@ -1819,7 +1824,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onMessageWaitingIndicatorChanged(mwi); } catch (RemoteException ex) { @@ -1846,7 +1851,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_USER_MOBILE_DATA_STATE_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onUserMobileDataStateChanged(state); } catch (RemoteException ex) { @@ -1885,7 +1890,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED) - && idMatchWithoutDefaultPhoneCheck(r.subId, subId)) { + && idMatch(r, subId, phoneId)) { try { if (!mConfigurationProvider.isDisplayInfoNrAdvancedSupported( r.callingPackage, Binder.getCallingUserHandle())) { @@ -1937,7 +1942,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_CALL_FORWARDING_INDICATOR_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onCallForwardingIndicatorChanged(cfi); } catch (RemoteException ex) { @@ -1966,7 +1971,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { // Notify by correct subId. if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_DATA_ACTIVITY_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onDataActivity(state); } catch (RemoteException ex) { @@ -1995,42 +2000,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { ApnSetting apnSetting = preciseState.getApnSetting(); - int apnTypes = apnSetting.getApnTypeBitmask(); - int state = preciseState.getState(); - int networkType = preciseState.getNetworkType(); - synchronized (mRecords) { if (validatePhoneId(phoneId)) { - // We only call the callback when the change is for default APN type. - if ((ApnSetting.TYPE_DEFAULT & apnTypes) != 0 - && (mDataConnectionState[phoneId] != state - || mDataConnectionNetworkType[phoneId] != networkType)) { - String str = "onDataConnectionStateChanged(" - + TelephonyUtils.dataStateToString(state) - + ", " + getNetworkTypeName(networkType) - + ") subId=" + subId + ", phoneId=" + phoneId; - log(str); - mLocalLog.log(str); - for (Record r : mRecords) { - if (r.matchTelephonyCallbackEvent( - TelephonyCallback.EVENT_DATA_CONNECTION_STATE_CHANGED) - && idMatch(r.subId, subId, phoneId)) { - try { - if (DBG) { - log("Notify data connection state changed on sub: " + subId); - } - r.callback.onDataConnectionStateChanged(state, networkType); - } catch (RemoteException ex) { - mRemoveList.add(r.binder); - } - } - } - handleRemoveListLocked(); - - mDataConnectionState[phoneId] = state; - mDataConnectionNetworkType[phoneId] = networkType; - } - Pair<Integer, ApnSetting> key = Pair.create(preciseState.getTransportType(), preciseState.getApnSetting()); PreciseDataConnectionState oldState = mPreciseDataConnectionStates.get(phoneId) @@ -2039,7 +2010,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onPreciseDataConnectionStateChanged(preciseState); } catch (RemoteException ex) { @@ -2062,6 +2033,73 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (preciseState.getState() != TelephonyManager.DATA_DISCONNECTED) { mPreciseDataConnectionStates.get(phoneId).put(key, preciseState); } + + // Note that below is just the workaround for reporting the correct data connection + // state. The actual fix should be put in the new data stack in T. + // TODO: Remove the code below in T. + + // Collect all possible candidate data connection state for internet. Key is the + // data connection state, value is the precise data connection state. + Map<Integer, PreciseDataConnectionState> internetConnections = new ArrayMap<>(); + if (preciseState.getState() == TelephonyManager.DATA_DISCONNECTED + && preciseState.getApnSetting().getApnTypes() + .contains(ApnSetting.TYPE_DEFAULT)) { + internetConnections.put(TelephonyManager.DATA_DISCONNECTED, preciseState); + } + for (Map.Entry<Pair<Integer, ApnSetting>, PreciseDataConnectionState> entry : + mPreciseDataConnectionStates.get(phoneId).entrySet()) { + if (entry.getKey().first == AccessNetworkConstants.TRANSPORT_TYPE_WWAN + && entry.getKey().second.getApnTypes() + .contains(ApnSetting.TYPE_DEFAULT)) { + internetConnections.put(entry.getValue().getState(), entry.getValue()); + } + } + + // If any internet data is in connected state, then report connected, then check + // suspended, connecting, disconnecting, and disconnected. The order is very + // important. + int[] statesInPriority = new int[]{TelephonyManager.DATA_CONNECTED, + TelephonyManager.DATA_SUSPENDED, TelephonyManager.DATA_CONNECTING, + TelephonyManager.DATA_DISCONNECTING, + TelephonyManager.DATA_DISCONNECTED}; + int state = TelephonyManager.DATA_DISCONNECTED; + int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN; + for (int s : statesInPriority) { + if (internetConnections.containsKey(s)) { + state = s; + networkType = internetConnections.get(s).getNetworkType(); + break; + } + } + + if (mDataConnectionState[phoneId] != state + || mDataConnectionNetworkType[phoneId] != networkType) { + String str = "onDataConnectionStateChanged(" + + TelephonyUtils.dataStateToString(state) + + ", " + TelephonyManager.getNetworkTypeName(networkType) + + ") subId=" + subId + ", phoneId=" + phoneId; + log(str); + mLocalLog.log(str); + for (Record r : mRecords) { + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_DATA_CONNECTION_STATE_CHANGED) + && idMatch(r, subId, phoneId)) { + try { + if (DBG) { + log("Notify data connection state changed on sub: " + subId); + } + r.callback.onDataConnectionStateChanged(state, networkType); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + + mDataConnectionState[phoneId] = state; + mDataConnectionNetworkType[phoneId] = networkType; + + handleRemoveListLocked(); + } } } } @@ -2086,7 +2124,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (validateEventAndUserLocked( r, TelephonyCallback.EVENT_CELL_LOCATION_CHANGED) - && idMatch(r.subId, subId, phoneId) + && idMatch(r, subId, phoneId) && (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE) && checkFineLocationAccess(r, Build.VERSION_CODES.Q))) { try { @@ -2140,7 +2178,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_PRECISE_CALL_STATE_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]); } catch (RemoteException ex) { @@ -2149,7 +2187,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } if (notifyCallAttributes && r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onCallAttributesChanged(mCallAttributes[phoneId]); } catch (RemoteException ex) { @@ -2174,7 +2212,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_CALL_DISCONNECT_CAUSE_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause[phoneId], mCallPreciseDisconnectCause[phoneId]); @@ -2199,7 +2237,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { if (DBG_LOC) { log("notifyImsCallDisconnectCause: mImsReasonInfo=" @@ -2231,7 +2269,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_SRVCC_STATE_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { if (DBG_LOC) { log("notifySrvccStateChanged: mSrvccState=" + state + " r=" + r); @@ -2260,7 +2298,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } if ((r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_OEM_HOOK_RAW)) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onOemHookRawEvent(rawData); } catch (RemoteException ex) { @@ -2340,7 +2378,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onRadioPowerStateChanged(state); } catch (RemoteException ex) { @@ -2370,7 +2408,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_EMERGENCY_NUMBER_LIST_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onEmergencyNumberListChanged(mEmergencyNumberList); if (VDBG) { @@ -2457,7 +2495,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onCallAttributesChanged(mCallAttributes[phoneId]); } catch (RemoteException ex) { @@ -2488,7 +2526,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_REGISTRATION_FAILURE) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onRegistrationFailed( checkFineLocationAccess(r, Build.VERSION_CODES.BASE) @@ -2531,7 +2569,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_BARRING_INFO_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { if (DBG_LOC) { log("notifyBarringInfo: mBarringInfo=" @@ -2576,7 +2614,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { if (DBG_LOC) { log("notifyPhysicalChannelConfig: mPhysicalChannelConfigs=" @@ -2643,7 +2681,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_DATA_ENABLED_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onDataEnabledChanged(enabled, reason); } catch (RemoteException ex) { @@ -2678,7 +2716,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { if (VDBG) { log("notifyAllowedNetworkTypesChanged: reason= " + reason @@ -2720,7 +2758,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatch(r, subId, phoneId)) { try { r.callback.onLinkCapacityEstimateChanged(linkCapacityEstimateList); } catch (RemoteException ex) { @@ -3189,33 +3227,24 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } /** - * If the registrant specified a subId, then we should only notify it if subIds match. - * If the registrant registered with DEFAULT subId, we should notify only when the related subId - * is default subId (which could be INVALID if there's no default subId). + * Match the sub id or phone id of the event to the record * - * This should be the correct way to check record ID match. in idMatch the record's phoneId is - * speculated based on subId passed by the registrant so it's not a good reference. - * But to avoid triggering potential regression only replace idMatch with it when an issue with - * idMatch is reported. Eventually this should replace all instances of idMatch. + * We follow the rules below: + * 1) If sub id of the event is invalid, phone id should be used. + * 2) The event on default sub should be notified to the records + * which register the default sub id. + * 3) Sub id should be exactly matched for all other cases. */ - private boolean idMatchWithoutDefaultPhoneCheck(int subIdInRecord, int subIdToNotify) { - if (subIdInRecord == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { - return (subIdToNotify == mDefaultSubId); - } else { - return (subIdInRecord == subIdToNotify); - } - } - - boolean idMatch(int rSubId, int subId, int phoneId) { + boolean idMatch(Record r, int subId, int phoneId) { - if(subId < 0) { - // Invalid case, we need compare phoneId with default one. - return (mDefaultPhoneId == phoneId); + if (subId < 0) { + // Invalid case, we need compare phoneId. + return (r.phoneId == phoneId); } - if(rSubId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { + if (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { return (subId == mDefaultSubId); } else { - return (rSubId == subId); + return (r.subId == subId); } } diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 85eadf5a5137..81627a05c9a4 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -1167,11 +1167,16 @@ final class UiModeManagerService extends SystemService { } private boolean doesPackageHaveCallingUid(@NonNull String packageName) { + int callingUid = mInjector.getCallingUid(); + int callingUserId = UserHandle.getUserId(callingUid); + final long ident = Binder.clearCallingIdentity(); try { - return getContext().getPackageManager().getPackageUid(packageName, 0) - == mInjector.getCallingUid(); + return getContext().getPackageManager().getPackageUidAsUser(packageName, + callingUserId) == callingUid; } catch (PackageManager.NameNotFoundException e) { return false; + } finally { + Binder.restoreCallingIdentity(ident); } } diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index ab6e8202ec8f..7119dc85e2e0 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -148,6 +148,8 @@ public class Watchdog { ); public static final String[] AIDL_INTERFACE_PREFIXES_OF_INTEREST = new String[] { + "android.hardware.biometrics.face.IFace/", + "android.hardware.biometrics.fingerprint.IFingerprint/", "android.hardware.light.ILights/", "android.hardware.power.stats.IPowerStats/", }; diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 6e3922746cba..3e905960d4df 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -87,13 +87,13 @@ import android.app.ActivityManagerInternal.ServiceNotificationPolicy; import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; -import android.app.ForegroundServiceDidNotStartInTimeException; import android.app.ForegroundServiceStartNotAllowedException; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException; import android.app.Service; import android.app.ServiceStartArgs; import android.app.admin.DevicePolicyEventLogger; @@ -158,6 +158,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.procstats.ServiceState; import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.os.SomeArgs; import com.android.internal.os.TransferPipe; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastPrintWriter; @@ -1278,7 +1279,7 @@ public final class ActiveServices { } void killMisbehavingService(ServiceRecord r, - int appUid, int appPid, String localPackageName) { + int appUid, int appPid, String localPackageName, int exceptionTypeId) { synchronized (mAm) { if (!r.destroying) { // This service is still alive, stop it. @@ -1292,8 +1293,8 @@ public final class ActiveServices { stopServiceLocked(found, false); } } - mAm.crashApplication(appUid, appPid, localPackageName, -1, - "Bad notification for startForeground", true /*force*/); + mAm.crashApplicationWithType(appUid, appPid, localPackageName, -1, + "Bad notification for startForeground", true /*force*/, exceptionTypeId); } } @@ -4385,9 +4386,12 @@ public final class ActiveServices { if (r.app != null) { Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG); - msg.obj = r.app; - msg.getData().putCharSequence( - ActivityManagerService.SERVICE_RECORD_KEY, r.toString()); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = r.app; + args.arg2 = r.toString(); + args.arg3 = r.getComponentName(); + + msg.obj = args; mAm.mHandler.sendMessage(msg); } } @@ -5457,11 +5461,14 @@ public final class ActiveServices { } } - void serviceForegroundCrash(ProcessRecord app, CharSequence serviceRecord) { - mAm.crashApplicationWithType(app.uid, app.getPid(), app.info.packageName, app.userId, + void serviceForegroundCrash(ProcessRecord app, String serviceRecord, + ComponentName service) { + mAm.crashApplicationWithTypeWithExtras( + app.uid, app.getPid(), app.info.packageName, app.userId, "Context.startForegroundService() did not then call Service.startForeground(): " + serviceRecord, false /*force*/, - ForegroundServiceDidNotStartInTimeException.TYPE_ID); + ForegroundServiceDidNotStartInTimeException.TYPE_ID, + ForegroundServiceDidNotStartInTimeException.createExtrasForService(service)); } void scheduleServiceTimeoutLocked(ProcessRecord proc) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 893c068dbdc9..3e3a4a51f372 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -31,6 +31,7 @@ import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY; import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManager.PROCESS_STATE_TOP; +import static android.app.ActivityManager.StopUserOnSwitch; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.AppOpsManager.OP_NONE; @@ -91,6 +92,7 @@ import static android.provider.Settings.Global.DEBUG_APP; import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS; import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER; import static android.text.format.DateUtils.DAY_IN_MILLIS; +import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; @@ -182,7 +184,6 @@ import android.app.PendingIntent; import android.app.ProcessMemoryState; import android.app.ProfilerInfo; import android.app.PropertyInvalidatedCache; -import android.app.RemoteServiceException; import android.app.SyncNotedAppOp; import android.app.WaitResult; import android.app.backup.BackupManager.OperationType; @@ -300,6 +301,7 @@ import android.text.style.SuggestionSpan; import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; +import android.util.FeatureFlagUtils; import android.util.IntArray; import android.util.Log; import android.util.Pair; @@ -341,6 +343,7 @@ import com.android.internal.os.BinderTransactionNameResolver; import com.android.internal.os.ByteTransferPipe; import com.android.internal.os.IResultReceiver; import com.android.internal.os.ProcessCpuTracker; +import com.android.internal.os.SomeArgs; import com.android.internal.os.TransferPipe; import com.android.internal.os.Zygote; import com.android.internal.policy.AttributeCache; @@ -1494,8 +1497,6 @@ public class ActivityManagerService extends IActivityManager.Stub static final int FIRST_BROADCAST_QUEUE_MSG = 200; - static final String SERVICE_RECORD_KEY = "servicerecord"; - /** * Flag whether the current user is a "monkey", i.e. whether * the UI is driven by a UI automation tool. @@ -1572,6 +1573,13 @@ public class ActivityManagerService extends IActivityManager.Stub private static final int INDEX_TOTAL_MEMTRACK_GL = 14; private static final int INDEX_LAST = 15; + /** + * Used to notify activity lifecycle events. + */ + @Nullable + volatile ActivityManagerInternal.VoiceInteractionManagerProvider + mVoiceInteractionManagerProvider; + final class UiHandler extends Handler { public UiHandler() { super(com.android.server.UiThread.get().getLooper(), null, true); @@ -1667,8 +1675,12 @@ public class ActivityManagerService extends IActivityManager.Stub mServices.serviceForegroundTimeout((ServiceRecord) msg.obj); } break; case SERVICE_FOREGROUND_CRASH_MSG: { - mServices.serviceForegroundCrash((ProcessRecord) msg.obj, - msg.getData().getCharSequence(SERVICE_RECORD_KEY)); + SomeArgs args = (SomeArgs) msg.obj; + mServices.serviceForegroundCrash( + (ProcessRecord) args.arg1, + (String) args.arg2, + (ComponentName) args.arg3); + args.recycle(); } break; case UPDATE_TIME_ZONE: { synchronized (mProcLock) { @@ -1908,6 +1920,14 @@ public class ActivityManagerService extends IActivityManager.Stub return mAppOpsService; } + /** + * Sets the internal voice interaction manager service. + */ + private void setVoiceInteractionManagerProvider( + @Nullable ActivityManagerInternal.VoiceInteractionManagerProvider provider) { + mVoiceInteractionManagerProvider = provider; + } + static class MemBinder extends Binder { ActivityManagerService mActivityManagerService; private final PriorityDump.PriorityDumper mPriorityDumper = @@ -2760,6 +2780,11 @@ public class ActivityManagerService extends IActivityManager.Stub || event == Event.ACTIVITY_DESTROYED)) { contentCaptureService.notifyActivityEvent(userId, activity, event); } + // TODO(b/201234353): Move the logic to client side. + if (mVoiceInteractionManagerProvider != null && (event == Event.ACTIVITY_PAUSED + || event == Event.ACTIVITY_RESUMED || event == Event.ACTIVITY_STOPPED)) { + mVoiceInteractionManagerProvider.notifyActivityEventChanged(); + } } /** @@ -3042,15 +3067,16 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void crashApplication(int uid, int initialPid, String packageName, int userId, - String message, boolean force) { - crashApplicationWithType(uid, initialPid, packageName, userId, message, force, - RemoteServiceException.TYPE_ID); + public void crashApplicationWithType(int uid, int initialPid, String packageName, int userId, + String message, boolean force, int exceptionTypeId) { + crashApplicationWithTypeWithExtras(uid, initialPid, packageName, userId, message, + force, exceptionTypeId, null); } @Override - public void crashApplicationWithType(int uid, int initialPid, String packageName, int userId, - String message, boolean force, int exceptionTypeId) { + public void crashApplicationWithTypeWithExtras(int uid, int initialPid, String packageName, + int userId, String message, boolean force, int exceptionTypeId, + @Nullable Bundle extras) { if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) != PackageManager.PERMISSION_GRANTED) { String msg = "Permission Denial: crashApplication() from pid=" @@ -3063,7 +3089,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized(this) { mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, userId, - message, force, exceptionTypeId); + message, force, exceptionTypeId, extras); } } @@ -4236,7 +4262,7 @@ public class ActivityManagerService extends IActivityManager.Stub didSomething |= mProcessList.killPackageProcessesLSP(packageName, appId, userId, ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit, - evenPersistent, true /* setRemoved */, + evenPersistent, true /* setRemoved */, uninstalling, packageName == null ? ApplicationExitInfo.REASON_USER_STOPPED : ApplicationExitInfo.REASON_USER_REQUESTED, ApplicationExitInfo.SUBREASON_UNKNOWN, @@ -4896,6 +4922,8 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void bootAnimationComplete() { + if (DEBUG_ALL) Slog.d(TAG, "bootAnimationComplete: Callers=" + Debug.getCallers(4)); + final boolean callFinishBooting; synchronized (this) { callFinishBooting = mCallFinishBooting; @@ -7284,6 +7312,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */, true /* callerWillRestart */, true /* doit */, true /* evenPersistent */, false /* setRemoved */, + false /* uninstalling */, ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_KILL_UID, reason != null ? reason : "kill uid"); @@ -7305,6 +7334,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */, true /* callerWillRestart */, true /* doit */, true /* evenPersistent */, false /* setRemoved */, + false /* uninstalling */, ApplicationExitInfo.REASON_PERMISSION_CHANGE, ApplicationExitInfo.SUBREASON_UNKNOWN, reason != null ? reason : "kill uid"); @@ -7718,7 +7748,7 @@ public class ActivityManagerService extends IActivityManager.Stub // On Automotive, at this point the system user has already been started and unlocked, // and some of the tasks we do here have already been done. So skip those in that case. - // TODO(b/132262830): this workdound shouldn't be necessary once we move the + // TODO(b/132262830, b/203885241): this workdound shouldn't be necessary once we move the // headless-user start logic to UserManager-land final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM; @@ -14360,6 +14390,8 @@ public class ActivityManagerService extends IActivityManager.Stub private void checkExcessivePowerUsage() { updateCpuStatsNow(); + final boolean monitorPhantomProcs = mSystemReady && FeatureFlagUtils.isEnabled(mContext, + SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS); synchronized (mProcLock) { final boolean doCpuKills = mLastPowerCheckUptime != 0; final long curUptime = SystemClock.uptimeMillis(); @@ -14385,9 +14417,11 @@ public class ActivityManagerService extends IActivityManager.Stub updateAppProcessCpuTimeLPr(uptimeSince, doCpuKills, checkDur, cpuLimit, app); - // Also check the phantom processes if there is any - updatePhantomProcessCpuTimeLPr( - uptimeSince, doCpuKills, checkDur, cpuLimit, app); + if (monitorPhantomProcs) { + // Also check the phantom processes if there is any + updatePhantomProcessCpuTimeLPr( + uptimeSince, doCpuKills, checkDur, cpuLimit, app); + } } }); } @@ -15188,6 +15222,21 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public String getSwitchingFromUserMessage() { + return mUserController.getSwitchingFromSystemUserMessage(); + } + + @Override + public String getSwitchingToUserMessage() { + return mUserController.getSwitchingToSystemUserMessage(); + } + + @Override + public void setStopUserOnSwitch(@StopUserOnSwitch int value) { + mUserController.setStopUserOnSwitch(value); + } + + @Override public int stopUser(final int userId, boolean force, final IStopUserCallback callback) { return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ false, /* callback= */ callback, /* keyEvictedCallback= */ null); @@ -16474,6 +16523,26 @@ public class ActivityManagerService extends IActivityManager.Stub return mProcessList.getIsolatedProcessesLocked(uid); } } + + /** @see ActivityManagerService#sendIntentSender */ + @Override + public int sendIntentSender(IIntentSender target, IBinder allowlistToken, int code, + Intent intent, String resolvedType, + IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { + return ActivityManagerService.this.sendIntentSender(target, allowlistToken, code, + intent, resolvedType, finishedReceiver, requiredPermission, options); + } + + @Override + public void setVoiceInteractionManagerProvider( + @Nullable ActivityManagerInternal.VoiceInteractionManagerProvider provider) { + ActivityManagerService.this.setVoiceInteractionManagerProvider(provider); + } + + @Override + public void setStopUserOnSwitch(int value) { + ActivityManagerService.this.setStopUserOnSwitch(value); + } } long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) { diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 685d606f8d41..ea28117a6a3d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -29,6 +29,8 @@ import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRI import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW; import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_MODERATE; import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING; import android.app.ActivityManager; @@ -44,6 +46,7 @@ import android.app.IStopUserCallback; import android.app.IUidObserver; import android.app.KeyguardManager; import android.app.ProfilerInfo; +import android.app.RemoteServiceException.CrashedByAdbException; import android.app.UserSwitchObserver; import android.app.WaitResult; import android.app.usage.AppStandbyInfo; @@ -100,6 +103,7 @@ import com.android.internal.util.HexDump; import com.android.internal.util.MemInfoReader; import com.android.server.am.LowMemDetector.MemFactor; import com.android.server.compat.PlatformCompat; +import com.android.server.utils.Slogf; import java.io.BufferedReader; import java.io.IOException; @@ -128,6 +132,10 @@ import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; final class ActivityManagerShellCommand extends ShellCommand { + + static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerShellCommand" : TAG_AM; + + public static final String NO_CLASS_ERROR_CODE = "Error type 3"; private static final String SHELL_PACKAGE_NAME = "com.android.shell"; @@ -323,6 +331,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runServiceRestartBackoff(pw); case "get-isolated-pids": return runGetIsolatedProcesses(pw); + case "set-stop-user-on-switch": + return runSetStopUserOnSwitch(pw); default: return handleDefaultCommands(cmd); } @@ -1145,7 +1155,8 @@ final class ActivityManagerShellCommand extends ShellCommand { } catch (NumberFormatException e) { packageName = arg; } - mInterface.crashApplication(-1, pid, packageName, userId, "shell-induced crash", false); + mInterface.crashApplicationWithType(-1, pid, packageName, userId, "shell-induced crash", + false, CrashedByAdbException.TYPE_ID); return 0; } @@ -3157,6 +3168,29 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + private int runSetStopUserOnSwitch(PrintWriter pw) throws RemoteException { + mInternal.enforceCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "setStopUserOnSwitch()"); + String arg = getNextArg(); + if (arg == null) { + Slogf.i(TAG, "setStopUserOnSwitch(): resetting to default value"); + mInternal.setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_DEFAULT); + pw.println("Reset to default value"); + return 0; + } + + boolean stop = Boolean.parseBoolean(arg); + int value = stop + ? ActivityManager.STOP_USER_ON_SWITCH_TRUE + : ActivityManager.STOP_USER_ON_SWITCH_FALSE; + + Slogf.i(TAG, "runSetStopUserOnSwitch(): setting to %d (%b)", value, stop); + mInternal.setStopUserOnSwitch(value); + pw.println("Set to " + stop); + + return 0; + } + private Resources getResources(PrintWriter pw) throws RemoteException { // system resources does not contain all the device configuration, construct it manually. Configuration config = mInterface.getConfiguration(); @@ -3489,6 +3523,10 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Shows the restart backoff policy state for <PACKAGE_NAME>."); pw.println(" get-isolated-pids <UID>"); pw.println(" Get the PIDs of isolated processes with packages in this <UID>"); + pw.println(" set-stop-user-on-switch [true|false]"); + pw.println(" Sets whether the current user (and its profiles) should be stopped" + + " when switching to a different user."); + pw.println(" Without arguments, it resets to the value defined by platform."); pw.println(); Intent.printIntentArgsHelp(pw, ""); } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index bcb42bb38495..0bf0fe2be246 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -27,6 +27,7 @@ import static com.android.server.am.ActivityManagerService.MY_PID; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.AnrController; @@ -40,6 +41,7 @@ import android.content.pm.VersionedPackage; import android.net.Uri; import android.os.Binder; import android.os.Build; +import android.os.Bundle; import android.os.Message; import android.os.Process; import android.os.SystemClock; @@ -489,7 +491,7 @@ class AppErrors { * @param message */ void scheduleAppCrashLocked(int uid, int initialPid, String packageName, int userId, - String message, boolean force, int exceptionTypeId) { + String message, boolean force, int exceptionTypeId, @Nullable Bundle extras) { ProcessRecord proc = null; // Figure out which process to kill. We don't trust that initialPid @@ -521,7 +523,7 @@ class AppErrors { return; } - proc.scheduleCrashLocked(message, exceptionTypeId); + proc.scheduleCrashLocked(message, exceptionTypeId, extras); if (force) { // If the app is responsive, the scheduled crash will happen as expected // and then the delayed summary kill will be a no-op. diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index 505edf989557..f51bec423787 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -19,6 +19,7 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.Process.FIRST_APPLICATION_UID; +import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS; import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL; import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW; @@ -77,6 +78,7 @@ import android.provider.DeviceConfig.Properties; import android.text.TextUtils; import android.util.ArrayMap; import android.util.DebugUtils; +import android.util.FeatureFlagUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -1038,6 +1040,7 @@ public class AppProfiler { mService.setProcessTrackerStateLOSP(app, trackerMemFactor, now); state.setProcStateChanged(false); } + trimMemoryUiHiddenIfNecessaryLSP(app); if (curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.isKilledByAm()) { if (trimMemoryLevel < curLevel[0] && (thread = app.getThread()) != null) { try { @@ -1080,24 +1083,6 @@ public class AppProfiler { } profile.setTrimMemoryLevel(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); } else { - if ((curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND - || state.isSystemNoUi()) && profile.hasPendingUiClean()) { - // If this application is now in the background and it - // had done UI, then give it the special trim level to - // have it free UI resources. - final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN; - if (trimMemoryLevel < level && (thread = app.getThread()) != null) { - try { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { - Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui " - + app.processName + " to " + level); - } - thread.scheduleTrimMemory(level); - } catch (RemoteException e) { - } - } - profile.setPendingUiClean(false); - } if (trimMemoryLevel < fgTrimLevel && (thread = app.getThread()) != null) { try { if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { @@ -1124,28 +1109,36 @@ public class AppProfiler { mService.setProcessTrackerStateLOSP(app, trackerMemFactor, now); state.setProcStateChanged(false); } - if ((state.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND - || state.isSystemNoUi()) && profile.hasPendingUiClean()) { - if (profile.getTrimMemoryLevel() < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN - && (thread = app.getThread()) != null) { - try { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { - Slog.v(TAG_OOM_ADJ, - "Trimming memory of ui hidden " + app.processName - + " to " + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); - } - thread.scheduleTrimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); - } catch (RemoteException e) { - } - } - profile.setPendingUiClean(false); - } + trimMemoryUiHiddenIfNecessaryLSP(app); profile.setTrimMemoryLevel(0); }); } return allChanged; } + @GuardedBy({"mService", "mProcLock"}) + private void trimMemoryUiHiddenIfNecessaryLSP(ProcessRecord app) { + if ((app.mState.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND + || app.mState.isSystemNoUi()) && app.mProfile.hasPendingUiClean()) { + // If this application is now in the background and it + // had done UI, then give it the special trim level to + // have it free UI resources. + final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN; + IApplicationThread thread; + if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) { + try { + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { + Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui " + + app.processName + " to " + level); + } + thread.scheduleTrimMemory(level); + } catch (RemoteException e) { + } + } + app.mProfile.setPendingUiClean(false); + } + } + @GuardedBy("mProcLock") long getLowRamTimeSinceIdleLPr(long now) { return mLowRamTimeSinceLastIdle + (mLowRamStartTime > 0 ? (now - mLowRamStartTime) : 0); @@ -1797,6 +1790,8 @@ public class AppProfiler { } void updateCpuStatsNow() { + final boolean monitorPhantomProcs = mService.mSystemReady && FeatureFlagUtils.isEnabled( + mService.mContext, SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS); synchronized (mProcessCpuTracker) { mProcessCpuMutexFree.set(false); final long now = SystemClock.uptimeMillis(); @@ -1835,7 +1830,7 @@ public class AppProfiler { } } - if (haveNewCpuStats) { + if (monitorPhantomProcs && haveNewCpuStats) { mService.mPhantomProcessList.updateProcessCpuStatesLocked(mProcessCpuTracker); } diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index 7ba032f683b8..cf4c8a356662 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -114,6 +114,9 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { private int mScreenState; @GuardedBy("this") + private int[] mPerDisplayScreenStates = null; + + @GuardedBy("this") private boolean mUseLatestStates = true; @GuardedBy("this") @@ -291,8 +294,8 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } @Override - public Future<?> scheduleSyncDueToScreenStateChange( - int flags, boolean onBattery, boolean onBatteryScreenOff, int screenState) { + public Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery, + boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) { synchronized (BatteryExternalStatsWorker.this) { if (mCurrentFuture == null || (mUpdateFlags & UPDATE_CPU) == 0) { mOnBattery = onBattery; @@ -301,6 +304,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } // always update screen state mScreenState = screenState; + mPerDisplayScreenStates = perDisplayScreenStates; return scheduleSyncLocked("screen-state", flags); } } @@ -432,6 +436,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { final boolean onBattery; final boolean onBatteryScreenOff; final int screenState; + final int[] displayScreenStates; final boolean useLatestStates; synchronized (BatteryExternalStatsWorker.this) { updateFlags = mUpdateFlags; @@ -440,6 +445,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { onBattery = mOnBattery; onBatteryScreenOff = mOnBatteryScreenOff; screenState = mScreenState; + displayScreenStates = mPerDisplayScreenStates; useLatestStates = mUseLatestStates; mUpdateFlags = 0; mCurrentReason = null; @@ -461,7 +467,8 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } try { updateExternalStatsLocked(reason, updateFlags, onBattery, - onBatteryScreenOff, screenState, useLatestStates); + onBatteryScreenOff, screenState, displayScreenStates, + useLatestStates); } finally { if (DEBUG) { Slog.d(TAG, "end updateExternalStatsSync"); @@ -506,7 +513,8 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { @GuardedBy("mWorkerLock") private void updateExternalStatsLocked(final String reason, int updateFlags, boolean onBattery, - boolean onBatteryScreenOff, int screenState, boolean useLatestStates) { + boolean onBatteryScreenOff, int screenState, int[] displayScreenStates, + boolean useLatestStates) { // We will request data from external processes asynchronously, and wait on a timeout. SynchronousResultReceiver wifiReceiver = null; SynchronousResultReceiver bluetoothReceiver = null; @@ -659,11 +667,12 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { // Inform mStats about each applicable measured energy (unless addressed elsewhere). if (measuredEnergyDeltas != null) { - final long displayChargeUC = measuredEnergyDeltas.displayChargeUC; - if (displayChargeUC != MeasuredEnergySnapshot.UNAVAILABLE) { - // If updating, pass in what BatteryExternalStatsWorker thinks screenState is. - mStats.updateDisplayMeasuredEnergyStatsLocked(displayChargeUC, screenState, - elapsedRealtime); + final long[] displayChargeUC = measuredEnergyDeltas.displayChargeUC; + if (displayChargeUC != null && displayChargeUC.length > 0) { + // If updating, pass in what BatteryExternalStatsWorker thinks + // displayScreenStates is. + mStats.updateDisplayMeasuredEnergyStatsLocked(displayChargeUC, + displayScreenStates, elapsedRealtime); } final long gnssChargeUC = measuredEnergyDeltas.gnssChargeUC; @@ -948,6 +957,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { switch (consumer.type) { case EnergyConsumerType.OTHER: case EnergyConsumerType.CPU_CLUSTER: + case EnergyConsumerType.DISPLAY: break; default: Slog.w(TAG, "EnergyConsumer '" + consumer.name + "' has unexpected ordinal " diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index ae14ca7b66bd..8d10d562520a 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1215,7 +1215,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub mHandler.post(() -> { if (DBG) Slog.d(TAG, "begin noteScreenState"); synchronized (mStats) { - mStats.noteScreenStateLocked(state, elapsedRealtime, uptime, currentTime); + mStats.noteScreenStateLocked(0, state, elapsedRealtime, uptime, currentTime); } if (DBG) Slog.d(TAG, "end noteScreenState"); }); @@ -1230,7 +1230,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub final long uptime = SystemClock.uptimeMillis(); mHandler.post(() -> { synchronized (mStats) { - mStats.noteScreenBrightnessLocked(brightness, elapsedRealtime, uptime); + mStats.noteScreenBrightnessLocked(0, brightness, elapsedRealtime, uptime); } }); } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 94bf62f8b9b7..2da41070a6f4 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -20,7 +20,13 @@ import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY; import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE; import static android.text.TextUtils.formatSimple; -import static com.android.server.am.ActivityManagerDebugConfig.*; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU; import android.annotation.Nullable; import android.app.ActivityManager; @@ -29,6 +35,7 @@ import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.IApplicationThread; import android.app.PendingIntent; +import android.app.RemoteServiceException.CannotDeliverBroadcastException; import android.app.usage.UsageEvents.Event; import android.content.ComponentName; import android.content.ContentResolver; @@ -603,7 +610,8 @@ public final class BroadcastQueue { synchronized (mService) { Slog.w(TAG, "Can't deliver broadcast to " + app.processName + " (pid " + app.getPid() + "). Crashing it."); - app.scheduleCrashLocked("can't deliver broadcast"); + app.scheduleCrashLocked("can't deliver broadcast", + CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null); } throw ex; } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index c641bf04731f..2761a7c6d937 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -612,6 +612,8 @@ public final class CachedAppOptimizer { */ static private native void compactProcess(int pid, int compactionFlags); + static private native void cancelCompaction(); + /** * Reads the flag value from DeviceConfig to determine whether app compaction * should be enabled, and starts the freeze/compaction thread if needed. @@ -999,7 +1001,7 @@ public final class CachedAppOptimizer { void unfreezeTemporarily(ProcessRecord app) { if (mUseFreezer) { synchronized (mProcLock) { - if (app.mOptRecord.isFrozen()) { + if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) { unfreezeAppLSP(app); freezeAppAsyncLSP(app); } @@ -1125,6 +1127,26 @@ public final class CachedAppOptimizer { } } + @GuardedBy({"mService", "mProcLock"}) + void onOomAdjustChanged(int oldAdj, int newAdj, ProcessRecord app) { + // Cancel any currently executing compactions + // if the process moved out of cached state + if (DefaultProcessDependencies.mPidCompacting == app.mPid && newAdj < oldAdj + && newAdj < ProcessList.CACHED_APP_MIN_ADJ) { + cancelCompaction(); + } + + // Perform a minor compaction when a perceptible app becomes the prev/home app + // Perform a major compaction when any app enters cached + if (oldAdj <= ProcessList.PERCEPTIBLE_APP_ADJ + && (newAdj == ProcessList.PREVIOUS_APP_ADJ || newAdj == ProcessList.HOME_APP_ADJ)) { + compactAppSome(app); + } else if (newAdj >= ProcessList.CACHED_APP_MIN_ADJ + && newAdj <= ProcessList.CACHED_APP_MAX_ADJ) { + compactAppFull(app); + } + } + @VisibleForTesting static final class LastCompactionStats { private final long[] mRssAfterCompaction; @@ -1167,6 +1189,13 @@ public final class CachedAppOptimizer { name = proc.processName; opt.setHasPendingCompact(false); + if (mAm.mInternal.isPendingTopUid(proc.uid)) { + // In case the OOM Adjust has not yet been propagated we see if this is + // pending on becoming top app in which case we should not compact. + Slog.e(TAG_AM, "Skip compaction since UID is active for " + name); + return; + } + // don't compact if the process has returned to perceptible // and this is only a cached/home/prev compaction if ((pendingAction == COMPACT_PROCESS_SOME @@ -1576,6 +1605,8 @@ public final class CachedAppOptimizer { * Default implementation for ProcessDependencies, public vor visibility to OomAdjuster class. */ private static final class DefaultProcessDependencies implements ProcessDependencies { + public static int mPidCompacting = -1; + // Get memory RSS from process. @Override public long[] getRss(int pid) { @@ -1585,6 +1616,7 @@ public final class CachedAppOptimizer { // Compact process. @Override public void performCompaction(String action, int pid) throws IOException { + mPidCompacting = pid; if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])) { compactProcess(pid, COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG); } else if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FILE])) { @@ -1592,6 +1624,7 @@ public final class CachedAppOptimizer { } else if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) { compactProcess(pid, COMPACT_ACTION_ANON_FLAG); } + mPidCompacting = -1; } } } diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java index a9fca4f24026..0359aa531c64 100644 --- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java +++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java @@ -49,6 +49,9 @@ public class MeasuredEnergySnapshot { /** Number of ordinals for {@link EnergyConsumerType#CPU_CLUSTER}. */ private final int mNumCpuClusterOrdinals; + /** Number of ordinals for {@link EnergyConsumerType#DISPLAY}. */ + private final int mNumDisplayOrdinals; + /** Number of ordinals for {@link EnergyConsumerType#OTHER}. */ private final int mNumOtherOrdinals; @@ -95,6 +98,7 @@ public class MeasuredEnergySnapshot { mNumCpuClusterOrdinals = calculateNumOrdinals(EnergyConsumerType.CPU_CLUSTER, idToConsumerMap); + mNumDisplayOrdinals = calculateNumOrdinals(EnergyConsumerType.DISPLAY, idToConsumerMap); mNumOtherOrdinals = calculateNumOrdinals(EnergyConsumerType.OTHER, idToConsumerMap); mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals); } @@ -108,7 +112,7 @@ public class MeasuredEnergySnapshot { public long[] cpuClusterChargeUC = null; /** The chargeUC for {@link EnergyConsumerType#DISPLAY}. */ - public long displayChargeUC = UNAVAILABLE; + public long[] displayChargeUC = null; /** The chargeUC for {@link EnergyConsumerType#GNSS}. */ public long gnssChargeUC = UNAVAILABLE; @@ -212,7 +216,10 @@ public class MeasuredEnergySnapshot { break; case EnergyConsumerType.DISPLAY: - output.displayChargeUC = deltaChargeUC; + if (output.displayChargeUC == null) { + output.displayChargeUC = new long[mNumDisplayOrdinals]; + } + output.displayChargeUC[ordinal] = deltaChargeUC; break; case EnergyConsumerType.GNSS: diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index d993772b4f3b..e94c2fa60fc1 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -1711,7 +1711,7 @@ public class OomAdjuster { int schedGroup; int procState; int cachedAdjSeq; - int capability = 0; + int capability = cycleReEval ? app.mState.getCurCapability() : 0; boolean foregroundActivities = false; boolean hasVisibleActivities = false; @@ -2115,10 +2115,6 @@ public class OomAdjuster { } if ((cr.flags & Context.BIND_WAIVE_PRIORITY) == 0) { - if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { - continue; - } - if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { capability |= cstate.getCurCapability(); } @@ -2139,6 +2135,10 @@ public class OomAdjuster { } } + if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { + continue; + } + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { // If the other app is cached for any reason, for purposes here // we are going to consider it empty. The specific cached state @@ -2703,18 +2703,9 @@ public class OomAdjuster { // don't compact during bootup if (mCachedAppOptimizer.useCompaction() && mService.mBooted) { // Cached and prev/home compaction + // reminder: here, setAdj is previous state, curAdj is upcoming state if (state.getCurAdj() != state.getSetAdj()) { - // Perform a minor compaction when a perceptible app becomes the prev/home app - // Perform a major compaction when any app enters cached - // reminder: here, setAdj is previous state, curAdj is upcoming state - if (state.getSetAdj() <= ProcessList.PERCEPTIBLE_APP_ADJ - && (state.getCurAdj() == ProcessList.PREVIOUS_APP_ADJ - || state.getCurAdj() == ProcessList.HOME_APP_ADJ)) { - mCachedAppOptimizer.compactAppSome(app); - } else if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ - && state.getCurAdj() <= ProcessList.CACHED_APP_MAX_ADJ) { - mCachedAppOptimizer.compactAppFull(app); - } + mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app); } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE && state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ // Because these can fire independent of oom_adj/procstate changes, we need diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java index b07684c9a004..2ec1aedd18f9 100644 --- a/services/core/java/com/android/server/am/PhantomProcessList.java +++ b/services/core/java/com/android/server/am/PhantomProcessList.java @@ -18,6 +18,7 @@ package com.android.server.am; import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; +import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; @@ -28,6 +29,7 @@ import android.app.ApplicationExitInfo.SubReason; import android.os.Handler; import android.os.Process; import android.os.StrictMode; +import android.util.FeatureFlagUtils; import android.util.Slog; import android.util.SparseArray; @@ -419,6 +421,10 @@ public final class PhantomProcessList { * order of the oom adjs of their parent process. */ void trimPhantomProcessesIfNecessary() { + if (!mService.mSystemReady || !FeatureFlagUtils.isEnabled(mService.mContext, + SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS)) { + return; + } synchronized (mService.mProcLock) { synchronized (mLock) { mTrimPhantomProcessScheduled = false; diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index ee226f6ae6c4..29ac8738eeab 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -375,6 +375,16 @@ public final class ProcessList { private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id. /** + * Native heap allocations in AppZygote process and its descendants will now have a + * non-zero tag in the most significant byte. + * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged + * Pointers</a> + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S) + private static final long NATIVE_HEAP_POINTER_TAGGING_APP_ZYGOTE = 207557677; + + /** * Enable asynchronous (ASYNC) memory tag checking in this process. This * flag will only have an effect on hardware supporting the ARM Memory * Tagging Extension (MTE). @@ -1744,6 +1754,16 @@ public final class ProcessList { return level; } + private int decideTaggingLevelForAppZygote(ProcessRecord app) { + int level = decideTaggingLevel(app); + // TBI ("fake" pointer tagging) in AppZygote is controlled by a separate compat feature. + if (!mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING_APP_ZYGOTE, app.info) + && level == Zygote.MEMORY_TAG_LEVEL_TBI) { + level = Zygote.MEMORY_TAG_LEVEL_NONE; + } + return level; + } + private int decideGwpAsanLevel(ProcessRecord app) { // Look at the process attribute first. if (app.processInfo != null @@ -2244,7 +2264,8 @@ public final class ProcessList { // not the calling one. appInfo.packageName = app.getHostingRecord().getDefiningPackageName(); appInfo.uid = uid; - appZygote = new AppZygote(appInfo, uid, firstUid, lastUid); + int runtimeFlags = decideTaggingLevelForAppZygote(app); + appZygote = new AppZygote(appInfo, uid, firstUid, lastUid, runtimeFlags); mAppZygotes.put(app.info.processName, uid, appZygote); zygoteProcessList = new ArrayList<ProcessRecord>(); mAppZygoteProcesses.put(appZygote, zygoteProcessList); @@ -2766,8 +2787,8 @@ public final class ProcessList { int reasonCode, int subReason, String reason) { return killPackageProcessesLSP(packageName, appId, userId, minOomAdj, false /* callerWillRestart */, true /* allowRestart */, true /* doit */, - false /* evenPersistent */, false /* setRemoved */, reasonCode, - subReason, reason); + false /* evenPersistent */, false /* setRemoved */, false /* uninstalling */, + reasonCode, subReason, reason); } @GuardedBy("mService") @@ -2800,9 +2821,10 @@ public final class ProcessList { @GuardedBy({"mService", "mProcLock"}) boolean killPackageProcessesLSP(String packageName, int appId, int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart, - boolean doit, boolean evenPersistent, boolean setRemoved, int reasonCode, - int subReason, String reason) { - ArrayList<ProcessRecord> procs = new ArrayList<>(); + boolean doit, boolean evenPersistent, boolean setRemoved, boolean uninstalling, + int reasonCode, int subReason, String reason) { + final PackageManagerInternal pm = mService.getPackageManagerInternal(); + final ArrayList<Pair<ProcessRecord, Boolean>> procs = new ArrayList<>(); // Remove all processes this package may have touched: all with the // same UID (except for the system or root user), and all whose name @@ -2819,7 +2841,18 @@ public final class ProcessList { } if (app.isRemoved()) { if (doit) { - procs.add(app); + boolean shouldAllowRestart = false; + if (!uninstalling && packageName != null) { + // This package has a dependency on the given package being stopped, + // while it's not being frozen nor uninstalled, allow to restart it. + shouldAllowRestart = !app.getPkgList().containsKey(packageName) + && app.getPkgDeps() != null + && app.getPkgDeps().contains(packageName) + && app.info != null + && !pm.isPackageFrozen(app.info.packageName, app.uid, + app.userId); + } + procs.add(new Pair<>(app, shouldAllowRestart)); } continue; } @@ -2834,6 +2867,8 @@ public final class ProcessList { continue; } + boolean shouldAllowRestart = false; + // If no package is specified, we call all processes under the // give user id. if (packageName == null) { @@ -2855,9 +2890,16 @@ public final class ProcessList { if (userId != UserHandle.USER_ALL && app.userId != userId) { continue; } - if (!app.getPkgList().containsKey(packageName) && !isDep) { + final boolean isInPkgList = app.getPkgList().containsKey(packageName); + if (!isInPkgList && !isDep) { continue; } + if (!isInPkgList && isDep && !uninstalling && app.info != null + && !pm.isPackageFrozen(app.info.packageName, app.uid, app.userId)) { + // This package has a dependency on the given package being stopped, + // while it's not being frozen nor uninstalled, allow to restart it. + shouldAllowRestart = true; + } } // Process has passed all conditions, kill it! @@ -2867,14 +2909,15 @@ public final class ProcessList { if (setRemoved) { app.setRemoved(true); } - procs.add(app); + procs.add(new Pair<>(app, shouldAllowRestart)); } } int N = procs.size(); for (int i=0; i<N; i++) { - removeProcessLocked(procs.get(i), callerWillRestart, allowRestart, - reasonCode, subReason, reason); + final Pair<ProcessRecord, Boolean> proc = procs.get(i); + removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second, + reasonCode, subReason, reason); } killAppZygotesLocked(packageName, appId, userId, false /* force */); mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 13556c09215f..dd2c810750ac 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -26,12 +26,12 @@ import android.app.ApplicationExitInfo; import android.app.ApplicationExitInfo.Reason; import android.app.ApplicationExitInfo.SubReason; import android.app.IApplicationThread; -import android.app.RemoteServiceException; import android.content.pm.ApplicationInfo; import android.content.pm.ProcessInfo; import android.content.pm.VersionedPackage; import android.content.res.CompatibilityInfo; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; @@ -979,11 +979,6 @@ class ProcessRecord implements WindowProcessListener { return mServices.hasForegroundServices(); } - @GuardedBy("mService") - void scheduleCrashLocked(String message) { - scheduleCrashLocked(message, RemoteServiceException.TYPE_ID); - } - /** * Let an app process throw an exception on a binder thread, which typically crashes the * process, unless it has an unhandled exception handler. @@ -995,7 +990,7 @@ class ProcessRecord implements WindowProcessListener { * of its subclasses. */ @GuardedBy("mService") - void scheduleCrashLocked(String message, int exceptionTypeId) { + void scheduleCrashLocked(String message, int exceptionTypeId, @Nullable Bundle extras) { // Checking killedbyAm should keep it from showing the crash dialog if the process // was already dead for a good / normal reason. if (!mKilledByAm) { @@ -1006,7 +1001,7 @@ class ProcessRecord implements WindowProcessListener { } final long ident = Binder.clearCallingIdentity(); try { - mThread.scheduleCrash(message, exceptionTypeId); + mThread.scheduleCrash(message, exceptionTypeId, extras); } catch (RemoteException e) { // If it's already dead our work is done. If it's wedged just kill it. // We won't get the crash dialog or the error reporting. diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 17930ea9c93c..e36898fee387 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -28,6 +28,7 @@ import android.annotation.Nullable; import android.app.IApplicationThread; import android.app.Notification; import android.app.PendingIntent; +import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -1039,7 +1040,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // If it gave us a garbage notification, it doesn't // get to be foreground. ams.mServices.killMisbehavingService(record, - appUid, appPid, localPackageName); + appUid, appPid, localPackageName, + CannotPostForegroundServiceNotificationException.TYPE_ID); } } }); diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index ba3e1fb95e7d..b28b1a66cd97 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -19,6 +19,9 @@ package com.android.server.am; import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.app.ActivityManager.STOP_USER_ON_SWITCH_DEFAULT; +import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE; +import static android.app.ActivityManager.StopUserOnSwitch; import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM; import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP; import static android.app.ActivityManager.USER_OP_IS_CURRENT; @@ -368,6 +371,13 @@ class UserController implements Handler.Callback { @GuardedBy("mLock") private boolean mInitialized; + /** + * Defines the behavior of whether the background users should be stopped when the foreground + * user is switched. + */ + @GuardedBy("mLock") + private @StopUserOnSwitch int mStopUserOnSwitch = STOP_USER_ON_SWITCH_DEFAULT; + UserController(ActivityManagerService service) { this(new Injector(service)); } @@ -408,8 +418,31 @@ class UserController implements Handler.Callback { } } - private boolean shouldStopBackgroundUsersOnSwitch() { - int property = SystemProperties.getInt("fw.stop_bg_users_on_switch", -1); + void setStopUserOnSwitch(@StopUserOnSwitch int value) { + if (mInjector.checkCallingPermission(android.Manifest.permission.MANAGE_USERS) + == PackageManager.PERMISSION_DENIED && mInjector.checkCallingPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS) + == PackageManager.PERMISSION_DENIED) { + throw new SecurityException( + "You either need MANAGE_USERS or INTERACT_ACROSS_USERS permission to " + + "call setStopUserOnSwitch()"); + } + + synchronized (mLock) { + Slogf.i(TAG, "setStopUserOnSwitch(): %d -> %d", mStopUserOnSwitch, value); + mStopUserOnSwitch = value; + } + } + + private boolean shouldStopUserOnSwitch() { + synchronized (mLock) { + if (mStopUserOnSwitch != STOP_USER_ON_SWITCH_DEFAULT) { + final boolean value = mStopUserOnSwitch == STOP_USER_ON_SWITCH_TRUE; + Slogf.i(TAG, "shouldStopUserOnSwitch(): returning overridden value (%b)", value); + return value; + } + } + final int property = SystemProperties.getInt("fw.stop_bg_users_on_switch", -1); return property == -1 ? mDelayUserDataLocking : property == 1; } @@ -1759,7 +1792,8 @@ class UserController implements Handler.Callback { private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) { // The dialog will show and then initiate the user switch by calling startUserInForeground mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second, - getSwitchingFromSystemUserMessage(), getSwitchingToSystemUserMessage()); + getSwitchingFromSystemUserMessageUnchecked(), + getSwitchingToSystemUserMessageUnchecked()); } private void dispatchForegroundProfileChanged(@UserIdInt int userId) { @@ -1799,7 +1833,7 @@ class UserController implements Handler.Callback { mUserSwitchObservers.finishBroadcast(); } - private void stopBackgroundUsersOnSwitchIfEnforced(@UserIdInt int oldUserId) { + private void stopUserOnSwitchIfEnforced(@UserIdInt int oldUserId) { // Never stop system user if (oldUserId == UserHandle.USER_SYSTEM) { return; @@ -1807,18 +1841,17 @@ class UserController implements Handler.Callback { boolean hasRestriction = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId); synchronized (mLock) { - // If running in background is disabled or mStopBackgroundUsersOnSwitch mode, - // stop the user. - boolean disallowRunInBg = hasRestriction || shouldStopBackgroundUsersOnSwitch(); + // If running in background is disabled or mStopUserOnSwitch mode, stop the user. + boolean disallowRunInBg = hasRestriction || shouldStopUserOnSwitch(); if (!disallowRunInBg) { if (DEBUG_MU) { - Slogf.i(TAG, "stopBackgroundUsersIfEnforced() NOT stopping %d and related " - + "users", oldUserId); + Slogf.i(TAG, "stopUserOnSwitchIfEnforced() NOT stopping %d and related users", + oldUserId); } return; } if (DEBUG_MU) { - Slogf.i(TAG, "stopBackgroundUsersIfEnforced() stopping %d and related users", + Slogf.i(TAG, "stopUserOnSwitchIfEnforced() stopping %d and related users", oldUserId); } stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true, @@ -1921,7 +1954,7 @@ class UserController implements Handler.Callback { mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0)); stopGuestOrEphemeralUserIfBackground(oldUserId); - stopBackgroundUsersOnSwitchIfEnforced(oldUserId); + stopUserOnSwitchIfEnforced(oldUserId); } private void moveUserToForeground(UserState uss, int oldUserId, int newUserId) { @@ -2532,18 +2565,40 @@ class UserController implements Handler.Callback { } } - private String getSwitchingFromSystemUserMessage() { + // Called by AMS, must check permission + String getSwitchingFromSystemUserMessage() { + checkHasManageUsersPermission("getSwitchingFromSystemUserMessage()"); + + return getSwitchingFromSystemUserMessageUnchecked(); + } + + // Called by AMS, must check permission + String getSwitchingToSystemUserMessage() { + checkHasManageUsersPermission("getSwitchingToSystemUserMessage()"); + + return getSwitchingToSystemUserMessageUnchecked(); + } + + private String getSwitchingFromSystemUserMessageUnchecked() { synchronized (mLock) { return mSwitchingFromSystemUserMessage; } } - private String getSwitchingToSystemUserMessage() { + private String getSwitchingToSystemUserMessageUnchecked() { synchronized (mLock) { return mSwitchingToSystemUserMessage; } } + private void checkHasManageUsersPermission(String operation) { + if (mInjector.checkCallingPermission( + android.Manifest.permission.MANAGE_USERS) == PackageManager.PERMISSION_DENIED) { + throw new SecurityException( + "You need MANAGE_USERS permission to call " + operation); + } + } + void dumpDebug(ProtoOutputStream proto, long fieldId) { synchronized (mLock) { long token = proto.start(fieldId); @@ -2611,11 +2666,17 @@ class UserController implements Handler.Callback { pw.println(" mTargetUserId:" + mTargetUserId); pw.println(" mLastActiveUsers:" + mLastActiveUsers); pw.println(" mDelayUserDataLocking:" + mDelayUserDataLocking); - pw.println(" shouldStopBackgroundUsersOnSwitch:" - + shouldStopBackgroundUsersOnSwitch()); + pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch()); + pw.println(" mStopUserOnSwitch:" + mStopUserOnSwitch); pw.println(" mMaxRunningUsers:" + mMaxRunningUsers); pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled); pw.println(" mInitialized:" + mInitialized); + if (mSwitchingFromSystemUserMessage != null) { + pw.println(" mSwitchingFromSystemUserMessage: " + mSwitchingFromSystemUserMessage); + } + if (mSwitchingToSystemUserMessage != null) { + pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage); + } } } diff --git a/services/core/java/com/android/server/am/UserState.java b/services/core/java/com/android/server/am/UserState.java index 1fe76080fc6f..40fc3066e2bb 100644 --- a/services/core/java/com/android/server/am/UserState.java +++ b/services/core/java/com/android/server/am/UserState.java @@ -145,4 +145,11 @@ public final class UserState { proto.write(UserStateProto.SWITCHING, switching); proto.end(token); } + + @Override + public String toString() { + return "[UserState: id=" + mHandle.getIdentifier() + ", state=" + stateToString(state) + + ", lastState=" + stateToString(lastState) + ", switching=" + switching + + ", tokenProvided=" + tokenProvided + "]"; + } } diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationManagerInternal.java b/services/core/java/com/android/server/apphibernation/AppHibernationManagerInternal.java index b0335fe404f4..a3c9612e0e2c 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationManagerInternal.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationManagerInternal.java @@ -43,4 +43,9 @@ public abstract class AppHibernationManagerInternal { * @see AppHibernationService#setHibernatingGlobally */ public abstract void setHibernatingGlobally(String packageName, boolean isHibernating); + + /** + * @see AppHibernationService#isOatArtifactDeletionEnabled + */ + public abstract boolean isOatArtifactDeletionEnabled(); } diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index 19dcee4828dd..4d025c981ce9 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -200,6 +200,14 @@ public final class AppHibernationService extends SystemService { } /** + * Whether global hibernation should delete ART ahead-of-time compilation artifacts and prevent + * package manager from re-optimizing the APK. + */ + private boolean isOatArtifactDeletionEnabled() { + return mOatArtifactDeletionEnabled; + } + + /** * Whether a package is hibernating for a given user. * * @param packageName the package to check @@ -269,16 +277,16 @@ public final class AppHibernationService extends SystemService { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_APP_HIBERNATION, "Caller does not have MANAGE_APP_HIBERNATION permission."); - userId = handleIncomingUser(userId, methodName); - if (!checkUserStatesExist(userId, methodName)) { + final int realUserId = handleIncomingUser(userId, methodName); + if (!checkUserStatesExist(realUserId, methodName)) { return; } synchronized (mLock) { - final Map<String, UserLevelState> packageStates = mUserStates.get(userId); + final Map<String, UserLevelState> packageStates = mUserStates.get(realUserId); final UserLevelState pkgState = packageStates.get(packageName); if (pkgState == null) { Slog.e(TAG, String.format("Package %s is not installed for user %s", - packageName, userId)); + packageName, realUserId)); return; } @@ -286,13 +294,17 @@ public final class AppHibernationService extends SystemService { return; } + pkgState.hibernated = isHibernating; if (isHibernating) { - hibernatePackageForUser(packageName, userId, pkgState); + mBackgroundExecutor.execute(() -> hibernatePackageForUser(packageName, realUserId)); } else { - unhibernatePackageForUser(packageName, userId, pkgState); + mBackgroundExecutor.execute( + () -> unhibernatePackageForUser(packageName, realUserId)); + pkgState.lastUnhibernatedMs = System.currentTimeMillis(); } + final UserLevelState stateSnapshot = new UserLevelState(pkgState); - final int userIdSnapshot = userId; + final int userIdSnapshot = realUserId; mBackgroundExecutor.execute(() -> { FrameworkStatsLog.write( FrameworkStatsLog.USER_LEVEL_HIBERNATION_STATE_CHANGED, @@ -300,8 +312,8 @@ public final class AppHibernationService extends SystemService { userIdSnapshot, stateSnapshot.hibernated); }); - List<UserLevelState> states = new ArrayList<>(mUserStates.get(userId).values()); - mUserDiskStores.get(userId).scheduleWriteHibernationStates(states); + List<UserLevelState> states = new ArrayList<>(mUserStates.get(realUserId).values()); + mUserDiskStores.get(realUserId).scheduleWriteHibernationStates(states); } } @@ -326,10 +338,12 @@ public final class AppHibernationService extends SystemService { return; } if (state.hibernated != isHibernating) { + state.hibernated = isHibernating; if (isHibernating) { - hibernatePackageGlobally(packageName, state); + mBackgroundExecutor.execute(() -> hibernatePackageGlobally(packageName, state)); } else { - unhibernatePackageGlobally(packageName, state); + state.savedByte = 0; + state.lastUnhibernatedMs = System.currentTimeMillis(); } List<GlobalLevelState> states = new ArrayList<>(mGlobalHibernationStates.values()); mGlobalLevelHibernationDiskStore.scheduleWriteHibernationStates(states); @@ -366,20 +380,16 @@ public final class AppHibernationService extends SystemService { } /** - * Put an app into hibernation for a given user, allowing user-level optimizations to occur. - * - * @param pkgState package hibernation state + * Put an app into hibernation for a given user, allowing user-level optimizations to occur. Do + * not hold {@link #mLock} while calling this to avoid deadlock scenarios. */ - @GuardedBy("mLock") - private void hibernatePackageForUser(@NonNull String packageName, int userId, - @NonNull UserLevelState pkgState) { + private void hibernatePackageForUser(@NonNull String packageName, int userId) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage"); final long caller = Binder.clearCallingIdentity(); try { mIActivityManager.forceStopPackage(packageName, userId); mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId, null /* observer */); - pkgState.hibernated = true; } catch (RemoteException e) { throw new IllegalStateException( "Failed to hibernate due to manager not being available", e); @@ -390,16 +400,11 @@ public final class AppHibernationService extends SystemService { } /** - * Remove a package from hibernation for a given user. - * - * @param pkgState package hibernation state + * Remove a package from hibernation for a given user. Do not hold {@link #mLock} while calling + * this. */ - @GuardedBy("mLock") - private void unhibernatePackageForUser(@NonNull String packageName, int userId, - UserLevelState pkgState) { + private void unhibernatePackageForUser(@NonNull String packageName, int userId) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackage"); - pkgState.hibernated = false; - pkgState.lastUnhibernatedMs = System.currentTimeMillis(); final long caller = Binder.clearCallingIdentity(); // Deliver LOCKED_BOOT_COMPLETE AND BOOT_COMPLETE broadcast so app can re-register // their alarms/jobs/etc. @@ -450,29 +455,20 @@ public final class AppHibernationService extends SystemService { } /** - * Put a package into global hibernation, optimizing its storage at a package / APK level. + * Put a package into global hibernation, optimizing its storage at a package / APK level. Do + * not hold {@link #mLock} while calling this. */ - @GuardedBy("mLock") private void hibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackageGlobally"); + long savedBytes = 0; if (mOatArtifactDeletionEnabled) { - state.savedByte = Math.max( + savedBytes = Math.max( mPackageManagerInternal.deleteOatArtifactsOfPackage(packageName), 0); } - state.hibernated = true; - Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); - } - - /** - * Unhibernate a package from global hibernation. - */ - @GuardedBy("mLock") - private void unhibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) { - Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackageGlobally"); - state.hibernated = false; - state.savedByte = 0; - state.lastUnhibernatedMs = System.currentTimeMillis(); + synchronized (mLock) { + state.savedByte = savedBytes; + } Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } @@ -742,6 +738,11 @@ public final class AppHibernationService extends SystemService { public boolean isHibernatingGlobally(String packageName) { return mService.isHibernatingGlobally(packageName); } + + @Override + public boolean isOatArtifactDeletionEnabled() { + return mService.isOatArtifactDeletionEnabled(); + } } private final AppHibernationServiceStub mServiceStub = new AppHibernationServiceStub(this); diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 64b9bd98a2fc..6d29c379d1b1 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -1314,6 +1314,7 @@ public class AppOpsService extends IAppOpsService.Stub { event.getAttributionFlags(), event.getAttributionChainId()); } + events = isRunning ? mInProgressEvents : mPausedInProgressEvents; InProgressStartOpEvent newEvent = events.get(binders.get(i)); if (newEvent != null) { newEvent.numUnfinishedStarts += numPreviousUnfinishedStarts - 1; diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index e4e6a5424efa..3817ceaa1d15 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -328,7 +328,7 @@ import java.util.concurrent.atomic.AtomicBoolean; } boolean isBtScoRequested = isBluetoothScoRequested(); - if (isBtScoRequested && !wasBtScoRequested) { + if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) { if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) { Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for pid: " + pid); diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 31bd596497cd..6eb3235566f9 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -241,6 +241,9 @@ public class AudioDeviceInventory { //------------------------------------------------------------ /*package*/ void dump(PrintWriter pw, String prefix) { + pw.println("\n" + prefix + "BECOMING_NOISY_INTENT_DEVICES_SET="); + BECOMING_NOISY_INTENT_DEVICES_SET.forEach(device -> { + pw.print(" 0x" + Integer.toHexString(device)); }); pw.println("\n" + prefix + "Preferred devices for strategy:"); mPreferredDevices.forEach((strategy, device) -> { pw.println(" " + prefix + "strategy:" + strategy + " device:" + device); }); @@ -1254,10 +1257,13 @@ public class AudioDeviceInventory { state == AudioService.CONNECTION_STATE_CONNECTED ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED); if (state != AudioService.CONNECTION_STATE_DISCONNECTED) { + Log.i(TAG, "not sending NOISY: state=" + state); mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return return 0; } if (!BECOMING_NOISY_INTENT_DEVICES_SET.contains(device)) { + Log.i(TAG, "not sending NOISY: device=0x" + Integer.toHexString(device) + + " not in set " + BECOMING_NOISY_INTENT_DEVICES_SET); mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return return 0; } @@ -1267,18 +1273,24 @@ public class AudioDeviceInventory { if (((di.mDeviceType & AudioSystem.DEVICE_BIT_IN) == 0) && BECOMING_NOISY_INTENT_DEVICES_SET.contains(di.mDeviceType)) { devices.add(di.mDeviceType); + Log.i(TAG, "NOISY: adding 0x" + Integer.toHexString(di.mDeviceType)); } } if (musicDevice == AudioSystem.DEVICE_NONE) { musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); + Log.i(TAG, "NOISY: musicDevice changing from NONE to 0x" + + Integer.toHexString(musicDevice)); } // always ignore condition on device being actually used for music when in communication // because music routing is altered in this case. // also checks whether media routing if affected by a dynamic policy or mirroring - if (((device == musicDevice) || mDeviceBroker.isInCommunication()) - && AudioSystem.isSingleAudioDeviceType(devices, device) - && !mDeviceBroker.hasMediaDynamicPolicy() + final boolean inCommunication = mDeviceBroker.isInCommunication(); + final boolean singleAudioDeviceType = AudioSystem.isSingleAudioDeviceType(devices, device); + final boolean hasMediaDynamicPolicy = mDeviceBroker.hasMediaDynamicPolicy(); + if (((device == musicDevice) || inCommunication) + && singleAudioDeviceType + && !hasMediaDynamicPolicy && (musicDevice != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX)) { if (!mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0 /*not looking in past*/) && !mDeviceBroker.hasAudioFocusUsers()) { @@ -1291,6 +1303,12 @@ public class AudioDeviceInventory { } mDeviceBroker.postBroadcastBecomingNoisy(); delay = SystemProperties.getInt("audio.sys.noisy.broadcast.delay", 700); + } else { + Log.i(TAG, "not sending NOISY: device:0x" + Integer.toHexString(device) + + " musicDevice:0x" + Integer.toHexString(musicDevice) + + " inComm:" + inCommunication + + " mediaPolicy:" + hasMediaDynamicPolicy + + " singleDevice:" + singleAudioDeviceType); } mmi.set(MediaMetrics.Property.DELAY_MS, delay).record(); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index c0b58998ddf1..c9bc6308ca8a 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -93,11 +93,16 @@ import android.media.ICommunicationDeviceDispatcher; import android.media.IPlaybackConfigDispatcher; import android.media.IRecordingConfigDispatcher; import android.media.IRingtonePlayer; +import android.media.ISpatializerCallback; +import android.media.ISpatializerHeadToSoundStagePoseCallback; +import android.media.ISpatializerHeadTrackingModeCallback; +import android.media.ISpatializerOutputCallback; import android.media.IStrategyPreferredDevicesDispatcher; import android.media.IVolumeController; import android.media.MediaMetrics; import android.media.MediaRecorder.AudioSource; import android.media.PlayerBase; +import android.media.Spatializer; import android.media.VolumePolicy; import android.media.audiofx.AudioEffect; import android.media.audiopolicy.AudioMix; @@ -198,7 +203,8 @@ import java.util.stream.Collectors; */ public class AudioService extends IAudioService.Stub implements AccessibilityManager.TouchExplorationStateChangeListener, - AccessibilityManager.AccessibilityServicesStateChangeListener { + AccessibilityManager.AccessibilityServicesStateChangeListener, + AudioSystemAdapter.OnRoutingUpdatedListener { private static final String TAG = "AS.AudioService"; @@ -241,7 +247,7 @@ public class AudioService extends IAudioService.Stub */ private static final int FLAG_ADJUST_VOLUME = 1; - private final Context mContext; + final Context mContext; private final ContentResolver mContentResolver; private final AppOpsManager mAppOps; @@ -318,12 +324,16 @@ public class AudioService extends IAudioService.Stub private static final int MSG_SET_A2DP_DEV_CONNECTION_STATE = 38; private static final int MSG_A2DP_DEV_CONFIG_CHANGE = 39; private static final int MSG_DISPATCH_AUDIO_MODE = 40; + private static final int MSG_ROUTING_UPDATED = 41; + private static final int MSG_INIT_HEADTRACKING_SENSORS = 42; + private static final int MSG_PERSIST_SPATIAL_AUDIO_ENABLED = 43; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) private static final int MSG_DISABLE_AUDIO_FOR_UID = 100; private static final int MSG_INIT_STREAMS_VOLUMES = 101; + private static final int MSG_INIT_SPATIALIZER = 102; // end of messages handled under wakelock // retry delay in case of failure to indicate system ready to AudioFlinger @@ -873,6 +883,8 @@ public class AudioService extends IAudioService.Stub mSfxHelper = new SoundEffectsHelper(mContext); + mSpatializerHelper = new SpatializerHelper(this, mAudioSystem); + mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator(); @@ -1034,6 +1046,8 @@ public class AudioService extends IAudioService.Stub mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false); + mHasSpatializerEffect = SystemProperties.getBoolean("ro.audio.spatializer_enabled", false); + // done with service initialization, continue additional work in our Handler thread queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES, 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); @@ -1046,6 +1060,9 @@ public class AudioService extends IAudioService.Stub mCachedParams.put("facing", "none"); mCachedParams.put("hdr_audio_channel_count", "0"); mCachedParams.put("hdr_audio_sampling_rate", "0"); + + queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_SPATIALIZER, + 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); } /** @@ -1235,6 +1252,25 @@ public class AudioService extends IAudioService.Stub updateVibratorInfos(); } + //----------------------------------------------------------------- + // routing monitoring from AudioSystemAdapter + @Override + public void onRoutingUpdatedFromNative() { + if (!mHasSpatializerEffect) { + return; + } + sendMsg(mAudioHandler, + MSG_ROUTING_UPDATED, + SENDMSG_REPLACE, 0, 0, null, + /*delay*/ 0); + } + + void monitorRoutingChanges(boolean enabled) { + mAudioSystem.setRoutingListener(enabled ? this : null); + } + + + //----------------------------------------------------------------- RoleObserver mRoleObserver; class RoleObserver implements OnRoleHoldersChangedListener { @@ -1439,6 +1475,11 @@ public class AudioService extends IAudioService.Stub } } + if (mHasSpatializerEffect) { + mSpatializerHelper.reset(/* featureEnabled */ isSpatialAudioEnabled()); + monitorRoutingChanges(true); + } + onIndicateSystemReady(); // indicate the end of reconfiguration phase to audio HAL AudioSystem.setParameters("restarting=false"); @@ -7617,6 +7658,19 @@ public class AudioService extends IAudioService.Stub mAudioEventWakeLock.release(); break; + case MSG_INIT_SPATIALIZER: + mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect); + if (mHasSpatializerEffect) { + mSpatializerHelper.setFeatureEnabled(isSpatialAudioEnabled()); + monitorRoutingChanges(true); + } + mAudioEventWakeLock.release(); + break; + + case MSG_INIT_HEADTRACKING_SENSORS: + mSpatializerHelper.onInitSensors(); + break; + case MSG_CHECK_MUSIC_ACTIVE: onCheckMusicActive((String) msg.obj); break; @@ -7749,6 +7803,14 @@ public class AudioService extends IAudioService.Stub case MSG_DISPATCH_AUDIO_MODE: dispatchMode(msg.arg1); break; + + case MSG_ROUTING_UPDATED: + mSpatializerHelper.onRoutingUpdated(); + break; + + case MSG_PERSIST_SPATIAL_AUDIO_ENABLED: + onPersistSpatialAudioEnabled(msg.arg1 == 1); + break; } } } @@ -8316,6 +8378,239 @@ public class AudioService extends IAudioService.Stub } //========================================================================================== + private final @NonNull SpatializerHelper mSpatializerHelper; + /** + * Initialized from property ro.audio.spatializer_enabled + * Should only be 1 when the device ships with a Spatializer effect + */ + private final boolean mHasSpatializerEffect; + /** + * Default value for the spatial audio feature + */ + private static final boolean SPATIAL_AUDIO_ENABLED_DEFAULT = true; + + /** + * persist in user settings whether the feature is enabled. + * Can change when {@link Spatializer#setEnabled(boolean)} is called and successfully + * changes the state of the feature + * @param featureEnabled + */ + void persistSpatialAudioEnabled(boolean featureEnabled) { + sendMsg(mAudioHandler, + MSG_PERSIST_SPATIAL_AUDIO_ENABLED, + SENDMSG_REPLACE, featureEnabled ? 1 : 0, 0, null, + /*delay ms*/ 100); + } + + void onPersistSpatialAudioEnabled(boolean enabled) { + Settings.Secure.putIntForUser(mContentResolver, + Settings.Secure.SPATIAL_AUDIO_ENABLED, enabled ? 1 : 0, + UserHandle.USER_CURRENT); + } + + boolean isSpatialAudioEnabled() { + return Settings.Secure.getIntForUser(mContentResolver, + Settings.Secure.SPATIAL_AUDIO_ENABLED, SPATIAL_AUDIO_ENABLED_DEFAULT ? 1 : 0, + UserHandle.USER_CURRENT) == 1; + } + + private void enforceModifyDefaultAudioEffectsPermission() { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Missing MODIFY_DEFAULT_AUDIO_EFFECTS permission"); + } + } + + /** + * Returns the immersive audio level that the platform is capable of + * @see Spatializer#getImmersiveAudioLevel() + */ + public int getSpatializerImmersiveAudioLevel() { + return mSpatializerHelper.getCapableImmersiveAudioLevel(); + } + + /** @see Spatializer#isEnabled() */ + public boolean isSpatializerEnabled() { + return mSpatializerHelper.isEnabled(); + } + + /** @see Spatializer#isAvailable() */ + public boolean isSpatializerAvailable() { + return mSpatializerHelper.isAvailable(); + } + + /** @see Spatializer#setSpatializerEnabled(boolean) */ + public void setSpatializerEnabled(boolean enabled) { + enforceModifyDefaultAudioEffectsPermission(); + mSpatializerHelper.setFeatureEnabled(enabled); + } + + /** @see Spatializer#canBeSpatialized() */ + public boolean canBeSpatialized( + @NonNull AudioAttributes attributes, @NonNull AudioFormat format) { + Objects.requireNonNull(attributes); + Objects.requireNonNull(format); + return mSpatializerHelper.canBeSpatialized(attributes, format); + } + + /** @see Spatializer.SpatializerInfoDispatcherStub */ + public void registerSpatializerCallback( + @NonNull ISpatializerCallback cb) { + Objects.requireNonNull(cb); + mSpatializerHelper.registerStateCallback(cb); + } + + /** @see Spatializer.SpatializerInfoDispatcherStub */ + public void unregisterSpatializerCallback( + @NonNull ISpatializerCallback cb) { + Objects.requireNonNull(cb); + mSpatializerHelper.unregisterStateCallback(cb); + } + + /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */ + public void registerSpatializerHeadTrackingCallback( + @NonNull ISpatializerHeadTrackingModeCallback cb) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(cb); + mSpatializerHelper.registerHeadTrackingModeCallback(cb); + } + + /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */ + public void unregisterSpatializerHeadTrackingCallback( + @NonNull ISpatializerHeadTrackingModeCallback cb) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(cb); + mSpatializerHelper.unregisterHeadTrackingModeCallback(cb); + } + + /** @see Spatializer#setOnHeadToSoundstagePoseUpdatedListener */ + public void registerHeadToSoundstagePoseCallback( + @NonNull ISpatializerHeadToSoundStagePoseCallback cb) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(cb); + mSpatializerHelper.registerHeadToSoundstagePoseCallback(cb); + } + + /** @see Spatializer#clearOnHeadToSoundstagePoseUpdatedListener */ + public void unregisterHeadToSoundstagePoseCallback( + @NonNull ISpatializerHeadToSoundStagePoseCallback cb) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(cb); + mSpatializerHelper.unregisterHeadToSoundstagePoseCallback(cb); + } + + /** @see Spatializer#getSpatializerCompatibleAudioDevices() */ + public @NonNull List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices() { + enforceModifyDefaultAudioEffectsPermission(); + return mSpatializerHelper.getCompatibleAudioDevices(); + } + + /** @see Spatializer#addSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */ + public void addSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(ada); + mSpatializerHelper.addCompatibleAudioDevice(ada); + } + + /** @see Spatializer#removeSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */ + public void removeSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(ada); + mSpatializerHelper.removeCompatibleAudioDevice(ada); + } + + /** @see Spatializer#getSupportedHeadTrackingModes() */ + public int[] getSupportedHeadTrackingModes() { + enforceModifyDefaultAudioEffectsPermission(); + return mSpatializerHelper.getSupportedHeadTrackingModes(); + } + + /** @see Spatializer#getHeadTrackingMode() */ + public int getActualHeadTrackingMode() { + enforceModifyDefaultAudioEffectsPermission(); + return mSpatializerHelper.getActualHeadTrackingMode(); + } + + /** @see Spatializer#getDesiredHeadTrackingMode() */ + public int getDesiredHeadTrackingMode() { + enforceModifyDefaultAudioEffectsPermission(); + return mSpatializerHelper.getDesiredHeadTrackingMode(); + } + + /** @see Spatializer#setGlobalTransform */ + public void setSpatializerGlobalTransform(@NonNull float[] transform) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(transform); + mSpatializerHelper.setGlobalTransform(transform); + } + + /** @see Spatializer#recenterHeadTracker() */ + public void recenterHeadTracker() { + enforceModifyDefaultAudioEffectsPermission(); + mSpatializerHelper.recenterHeadTracker(); + } + + /** @see Spatializer#setDesiredHeadTrackingMode */ + public void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) { + enforceModifyDefaultAudioEffectsPermission(); + switch(mode) { + case Spatializer.HEAD_TRACKING_MODE_DISABLED: + case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD: + case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE: + break; + default: + return; + } + mSpatializerHelper.setDesiredHeadTrackingMode(mode); + } + + /** @see Spatializer#setEffectParameter */ + public void setSpatializerParameter(int key, @NonNull byte[] value) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(value); + mSpatializerHelper.setEffectParameter(key, value); + } + + /** @see Spatializer#getEffectParameter */ + public void getSpatializerParameter(int key, @NonNull byte[] value) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(value); + mSpatializerHelper.getEffectParameter(key, value); + } + + /** @see Spatializer#getOutput */ + public int getSpatializerOutput() { + enforceModifyDefaultAudioEffectsPermission(); + return mSpatializerHelper.getOutput(); + } + + /** @see Spatializer#setOnSpatializerOutputChangedListener */ + public void registerSpatializerOutputCallback(ISpatializerOutputCallback cb) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(cb); + mSpatializerHelper.registerSpatializerOutputCallback(cb); + } + + /** @see Spatializer#clearOnSpatializerOutputChangedListener */ + public void unregisterSpatializerOutputCallback(ISpatializerOutputCallback cb) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(cb); + mSpatializerHelper.unregisterSpatializerOutputCallback(cb); + } + + /** + * post a message to schedule init/release of head tracking sensors + * whether to initialize or release sensors is based on the state of spatializer + */ + void postInitSpatializerHeadTrackingSensors() { + sendMsg(mAudioHandler, + MSG_INIT_HEADTRACKING_SENSORS, + SENDMSG_REPLACE, + /*arg1*/ 0, /*arg2*/ 0, TAG, /*delay*/ 0); + } + + //========================================================================================== private boolean readCameraSoundForced() { return SystemProperties.getBoolean("audio.camerasound.force", false) || mContext.getResources().getBoolean( @@ -8820,8 +9115,6 @@ public class AudioService extends IAudioService.Stub protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; - mAudioSystem.dump(pw); - sLifecycleLogger.dump(pw); if (mAudioHandler != null) { pw.println("\nMessage handler (watch for unhandled messages):"); @@ -8896,6 +9189,14 @@ public class AudioService extends IAudioService.Stub sVolumeLogger.dump(pw); pw.println("\n"); dumpSupportedSystemUsage(pw); + + pw.println("\n"); + pw.println("\nSpatial audio:"); + pw.println("mHasSpatializerEffect:" + mHasSpatializerEffect); + pw.println("isSpatializerEnabled:" + isSpatializerEnabled()); + pw.println("isSpatialAudioEnabled:" + isSpatialAudioEnabled()); + + mAudioSystem.dump(pw); } private void dumpSupportedSystemUsage(PrintWriter pw) { diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 6d567807f357..a2ba76b6fd6a 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -17,6 +17,7 @@ package com.android.server.audio; import android.annotation.NonNull; +import android.annotation.Nullable; import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; import android.media.AudioSystem; @@ -24,6 +25,8 @@ import android.media.audiopolicy.AudioMix; import android.os.SystemClock; import android.util.Log; +import com.android.internal.annotations.GuardedBy; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -59,6 +62,9 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback { private ConcurrentHashMap<AudioAttributes, ArrayList<AudioDeviceAttributes>> mDevicesForAttrCache; private int[] mMethodCacheHit; + private static final Object sRoutingListenerLock = new Object(); + @GuardedBy("sRoutingListenerLock") + private static @Nullable OnRoutingUpdatedListener sRoutingListener; /** * should be false except when trying to debug caching errors. When true, the value retrieved @@ -76,6 +82,23 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback { Log.d(TAG, "---- onRoutingUpdated (from native) ----------"); } invalidateRoutingCache(); + final OnRoutingUpdatedListener listener; + synchronized (sRoutingListenerLock) { + listener = sRoutingListener; + } + if (listener != null) { + listener.onRoutingUpdatedFromNative(); + } + } + + interface OnRoutingUpdatedListener { + void onRoutingUpdatedFromNative(); + } + + static void setRoutingListener(@Nullable OnRoutingUpdatedListener listener) { + synchronized (sRoutingListenerLock) { + sRoutingListener = listener; + } } /** @@ -501,11 +524,28 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback { * @param pw */ public void dump(PrintWriter pw) { + pw.println("\nAudioSystemAdapter:"); + pw.println(" mDevicesForStreamCache:"); + if (mDevicesForStreamCache != null) { + for (Integer stream : mDevicesForStreamCache.keySet()) { + pw.println("\t stream:" + stream + " device:" + + AudioSystem.getOutputDeviceName(mDevicesForStreamCache.get(stream))); + } + } + pw.println(" mDevicesForAttrCache:"); + if (mDevicesForAttrCache != null) { + for (AudioAttributes attr : mDevicesForAttrCache.keySet()) { + pw.println("\t" + attr); + for (AudioDeviceAttributes devAttr : mDevicesForAttrCache.get(attr)) { + pw.println("\t\t" + devAttr); + } + } + } + if (!ENABLE_GETDEVICES_STATS) { - // only stats in this dump + // only stats in the rest of this dump return; } - pw.println("\nAudioSystemAdapter:"); for (int i = 0; i < NB_MEASUREMENTS; i++) { pw.println(mMethodNames[i] + ": counter=" + mMethodCallCounter[i] diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java index ad7216600e61..d4f4245f19c5 100644 --- a/services/core/java/com/android/server/audio/RotationHelper.java +++ b/services/core/java/com/android/server/audio/RotationHelper.java @@ -17,33 +17,44 @@ package com.android.server.audio; import android.content.Context; +import android.hardware.devicestate.DeviceStateManager; +import android.hardware.devicestate.DeviceStateManager.FoldStateListener; import android.hardware.display.DisplayManager; import android.media.AudioSystem; import android.os.Handler; +import android.os.HandlerExecutor; import android.util.Log; import android.view.Surface; import android.view.WindowManager; /** * Class to handle device rotation events for AudioService, and forward device rotation - * to the audio HALs through AudioSystem. + * and folded state to the audio HALs through AudioSystem. * * The role of this class is to monitor device orientation changes, and upon rotation, * verify the UI orientation. In case of a change, send the new orientation, in increments * of 90deg, through AudioSystem. * + * Another role of this class is to track device folded state changes. In case of a + * change, send the new folded state through AudioSystem. + * * Note that even though we're responding to device orientation events, we always * query the display rotation so audio stays in sync with video/dialogs. This is * done with .getDefaultDisplay().getRotation() from WINDOW_SERVICE. + * + * We also monitor current display ID and audio is able to know which display is active. */ class RotationHelper { private static final String TAG = "AudioService.RotationHelper"; private static AudioDisplayListener sDisplayListener; + private static FoldStateListener sFoldStateListener; private static final Object sRotationLock = new Object(); + private static final Object sFoldStateLock = new Object(); private static int sDeviceRotation = Surface.ROTATION_0; // R/W synchronized on sRotationLock + private static boolean sDeviceFold = true; // R/W synchronized on sFoldStateLock private static Context sContext; private static Handler sHandler; @@ -67,11 +78,17 @@ class RotationHelper { ((DisplayManager) sContext.getSystemService(Context.DISPLAY_SERVICE)) .registerDisplayListener(sDisplayListener, sHandler); updateOrientation(); + + sFoldStateListener = new FoldStateListener(sContext, folded -> updateFoldState(folded)); + sContext.getSystemService(DeviceStateManager.class) + .registerCallback(new HandlerExecutor(sHandler), sFoldStateListener); } static void disable() { ((DisplayManager) sContext.getSystemService(Context.DISPLAY_SERVICE)) .unregisterDisplayListener(sDisplayListener); + sContext.getSystemService(DeviceStateManager.class) + .unregisterCallback(sFoldStateListener); } /** @@ -112,6 +129,22 @@ class RotationHelper { } /** + * publish the change of device folded state if any. + */ + static void updateFoldState(boolean newFolded) { + synchronized (sFoldStateLock) { + if (sDeviceFold != newFolded) { + sDeviceFold = newFolded; + if (newFolded) { + AudioSystem.setParameters("device_folded=on"); + } else { + AudioSystem.setParameters("device_folded=off"); + } + } + } + } + + /** * Uses android.hardware.display.DisplayManager.DisplayListener */ final static class AudioDisplayListener implements DisplayManager.DisplayListener { diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java new file mode 100644 index 000000000000..b47ea4f7a4b8 --- /dev/null +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -0,0 +1,986 @@ +/* + * Copyright (C) 2021 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.audio; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.media.AudioAttributes; +import android.media.AudioDeviceAttributes; +import android.media.AudioFormat; +import android.media.AudioSystem; +import android.media.INativeSpatializerCallback; +import android.media.ISpatializer; +import android.media.ISpatializerCallback; +import android.media.ISpatializerHeadToSoundStagePoseCallback; +import android.media.ISpatializerHeadTrackingCallback; +import android.media.ISpatializerHeadTrackingModeCallback; +import android.media.ISpatializerOutputCallback; +import android.media.SpatializationLevel; +import android.media.Spatializer; +import android.media.SpatializerHeadTrackingMode; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * A helper class to manage Spatializer related functionality + */ +public class SpatializerHelper { + + private static final String TAG = "AS.SpatializerHelper"; + private static final boolean DEBUG = true; + private static final boolean DEBUG_MORE = false; + + private static void logd(String s) { + if (DEBUG) { + Log.i(TAG, s); + } + } + + private final @NonNull AudioSystemAdapter mASA; + private final @NonNull AudioService mAudioService; + private @Nullable SensorManager mSensorManager; + + //------------------------------------------------------------ + /** head tracker sensor name */ + // TODO: replace with generic head tracker sensor name. + // the current implementation refers to the "google" namespace but will be replaced + // by an android name at the next API level revision, it is not Google-specific. + // Also see "TODO-HT" in onInitSensors() method + private static final String HEADTRACKER_SENSOR = + "com.google.hardware.sensor.hid_dynamic.headtracker"; + + // Spatializer state machine + private static final int STATE_UNINITIALIZED = 0; + private static final int STATE_NOT_SUPPORTED = 1; + private static final int STATE_DISABLED_UNAVAILABLE = 3; + private static final int STATE_ENABLED_UNAVAILABLE = 4; + private static final int STATE_ENABLED_AVAILABLE = 5; + private static final int STATE_DISABLED_AVAILABLE = 6; + private int mState = STATE_UNINITIALIZED; + + /** current level as reported by native Spatializer in callback */ + private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; + private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; + private int mSpatOutput = 0; + private @Nullable ISpatializer mSpat; + private @Nullable SpatializerCallback mSpatCallback; + private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback; + private @Nullable HelperDynamicSensorCallback mDynSensorCallback; + + // default attributes and format that determine basic availability of spatialization + private static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .build(); + private static final AudioFormat DEFAULT_FORMAT = new AudioFormat.Builder() + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .setSampleRate(48000) + .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) + .build(); + // device array to store the routing for the default attributes and format, size 1 because + // media is never expected to be duplicated + private static final AudioDeviceAttributes[] ROUTING_DEVICES = new AudioDeviceAttributes[1]; + + //--------------------------------------------------------------- + // audio device compatibility / enabled + + private final ArrayList<AudioDeviceAttributes> mCompatibleAudioDevices = new ArrayList<>(0); + + //------------------------------------------------------ + // initialization + SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa) { + mAudioService = mother; + mASA = asa; + } + + synchronized void init(boolean effectExpected) { + Log.i(TAG, "Initializing"); + if (!effectExpected) { + Log.i(TAG, "Setting state to STATE_NOT_SUPPORTED due to effect not expected"); + mState = STATE_NOT_SUPPORTED; + return; + } + if (mState != STATE_UNINITIALIZED) { + throw new IllegalStateException(("init() called in state:" + mState)); + } + // is there a spatializer? + mSpatCallback = new SpatializerCallback(); + final ISpatializer spat = AudioSystem.getSpatializer(mSpatCallback); + if (spat == null) { + Log.i(TAG, "init(): No Spatializer found"); + mState = STATE_NOT_SUPPORTED; + return; + } + // capabilities of spatializer? + try { + byte[] levels = spat.getSupportedLevels(); + if (levels == null + || levels.length == 0 + || (levels.length == 1 + && levels[0] == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE)) { + Log.e(TAG, "Spatializer is useless"); + mState = STATE_NOT_SUPPORTED; + return; + } + for (byte level : levels) { + logd("found support for level: " + level); + if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL) { + logd("Setting capable level to LEVEL_MULTICHANNEL"); + mCapableSpatLevel = level; + break; + } + } + } catch (RemoteException e) { + /* capable level remains at NONE*/ + } finally { + if (spat != null) { + try { + spat.release(); + } catch (RemoteException e) { /* capable level remains at NONE*/ } + } + } + if (mCapableSpatLevel == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { + mState = STATE_NOT_SUPPORTED; + return; + } + mState = STATE_DISABLED_UNAVAILABLE; + // note at this point mSpat is still not instantiated + } + + /** + * Like init() but resets the state and spatializer levels + * @param featureEnabled + */ + synchronized void reset(boolean featureEnabled) { + Log.i(TAG, "Resetting"); + mState = STATE_UNINITIALIZED; + mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; + init(true); + setFeatureEnabled(featureEnabled); + } + + //------------------------------------------------------ + // routing monitoring + void onRoutingUpdated() { + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + return; + case STATE_DISABLED_UNAVAILABLE: + case STATE_ENABLED_UNAVAILABLE: + case STATE_ENABLED_AVAILABLE: + case STATE_DISABLED_AVAILABLE: + break; + } + mASA.getDevicesForAttributes(DEFAULT_ATTRIBUTES).toArray(ROUTING_DEVICES); + final boolean able = + AudioSystem.canBeSpatialized(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, ROUTING_DEVICES); + logd("onRoutingUpdated: can spatialize media 5.1:" + able + + " on device:" + ROUTING_DEVICES[0]); + setDispatchAvailableState(able); + } + + //------------------------------------------------------ + // spatializer callback from native + private final class SpatializerCallback extends INativeSpatializerCallback.Stub { + + public void onLevelChanged(byte level) { + logd("SpatializerCallback.onLevelChanged level:" + level); + synchronized (SpatializerHelper.this) { + mSpatLevel = spatializationLevelToSpatializerInt(level); + } + // TODO use reported spat level to change state + + // init sensors + postInitSensors(); + } + + public void onOutputChanged(int output) { + logd("SpatializerCallback.onOutputChanged output:" + output); + int oldOutput; + synchronized (SpatializerHelper.this) { + oldOutput = mSpatOutput; + mSpatOutput = output; + } + if (oldOutput != output) { + dispatchOutputUpdate(output); + } + } + }; + + //------------------------------------------------------ + // spatializer head tracking callback from native + private final class SpatializerHeadTrackingCallback + extends ISpatializerHeadTrackingCallback.Stub { + public void onHeadTrackingModeChanged(byte mode) { + logd("SpatializerHeadTrackingCallback.onHeadTrackingModeChanged mode:" + mode); + int oldMode, newMode; + synchronized (this) { + oldMode = mActualHeadTrackingMode; + mActualHeadTrackingMode = headTrackingModeTypeToSpatializerInt(mode); + newMode = mActualHeadTrackingMode; + } + if (oldMode != newMode) { + dispatchActualHeadTrackingMode(newMode); + } + } + + public void onHeadToSoundStagePoseUpdated(float[] headToStage) { + if (headToStage == null) { + Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated" + + "null transform"); + return; + } + if (headToStage.length != 6) { + Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated" + + " invalid transform length" + headToStage.length); + return; + } + if (DEBUG_MORE) { + // 6 values * (4 digits + 1 dot + 2 brackets) = 42 characters + StringBuilder t = new StringBuilder(42); + for (float val : headToStage) { + t.append("[").append(String.format(Locale.ENGLISH, "%.3f", val)).append("]"); + } + logd("SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated headToStage:" + t); + } + dispatchPoseUpdate(headToStage); + } + }; + + //------------------------------------------------------ + // dynamic sensor callback + private final class HelperDynamicSensorCallback extends SensorManager.DynamicSensorCallback { + @Override + public void onDynamicSensorConnected(Sensor sensor) { + postInitSensors(); + } + + @Override + public void onDynamicSensorDisconnected(Sensor sensor) { + postInitSensors(); + } + } + + //------------------------------------------------------ + // compatible devices + /** + * @return a shallow copy of the list of compatible audio devices + */ + synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() { + return (List<AudioDeviceAttributes>) mCompatibleAudioDevices.clone(); + } + + synchronized void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { + if (!mCompatibleAudioDevices.contains(ada)) { + mCompatibleAudioDevices.add(ada); + } + } + + synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { + mCompatibleAudioDevices.remove(ada); + } + + //------------------------------------------------------ + // states + + synchronized boolean isEnabled() { + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + return false; + case STATE_ENABLED_UNAVAILABLE: + case STATE_ENABLED_AVAILABLE: + default: + return true; + } + } + + synchronized boolean isAvailable() { + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + return false; + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + default: + return true; + } + } + + synchronized void setFeatureEnabled(boolean enabled) { + switch (mState) { + case STATE_UNINITIALIZED: + if (enabled) { + throw(new IllegalStateException("Can't enable when uninitialized")); + } + return; + case STATE_NOT_SUPPORTED: + if (enabled) { + Log.e(TAG, "Can't enable when unsupported"); + } + return; + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + if (enabled) { + createSpat(); + break; + } else { + // already in disabled state + return; + } + case STATE_ENABLED_UNAVAILABLE: + case STATE_ENABLED_AVAILABLE: + if (!enabled) { + releaseSpat(); + break; + } else { + // already in enabled state + return; + } + } + setDispatchFeatureEnabledState(enabled); + } + + synchronized int getCapableImmersiveAudioLevel() { + return mCapableSpatLevel; + } + + final RemoteCallbackList<ISpatializerCallback> mStateCallbacks = + new RemoteCallbackList<ISpatializerCallback>(); + + synchronized void registerStateCallback( + @NonNull ISpatializerCallback callback) { + mStateCallbacks.register(callback); + } + + synchronized void unregisterStateCallback( + @NonNull ISpatializerCallback callback) { + mStateCallbacks.unregister(callback); + } + + /** + * precondition: mState = STATE_* + * isFeatureEnabled() != featureEnabled + * @param featureEnabled + */ + private synchronized void setDispatchFeatureEnabledState(boolean featureEnabled) { + if (featureEnabled) { + switch (mState) { + case STATE_DISABLED_UNAVAILABLE: + mState = STATE_ENABLED_UNAVAILABLE; + break; + case STATE_DISABLED_AVAILABLE: + mState = STATE_ENABLED_AVAILABLE; + break; + default: + throw(new IllegalStateException("Invalid mState:" + mState + + " for enabled true")); + } + } else { + switch (mState) { + case STATE_ENABLED_UNAVAILABLE: + mState = STATE_DISABLED_UNAVAILABLE; + break; + case STATE_ENABLED_AVAILABLE: + mState = STATE_DISABLED_AVAILABLE; + break; + default: + throw (new IllegalStateException("Invalid mState:" + mState + + " for enabled false")); + } + } + final int nbCallbacks = mStateCallbacks.beginBroadcast(); + for (int i = 0; i < nbCallbacks; i++) { + try { + mStateCallbacks.getBroadcastItem(i) + .dispatchSpatializerEnabledChanged(featureEnabled); + } catch (RemoteException e) { + Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e); + } + } + mStateCallbacks.finishBroadcast(); + mAudioService.persistSpatialAudioEnabled(featureEnabled); + } + + private synchronized void setDispatchAvailableState(boolean available) { + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + throw(new IllegalStateException( + "Should not update available state in state:" + mState)); + case STATE_DISABLED_UNAVAILABLE: + if (available) { + mState = STATE_DISABLED_AVAILABLE; + break; + } else { + // already in unavailable state + return; + } + case STATE_ENABLED_UNAVAILABLE: + if (available) { + mState = STATE_ENABLED_AVAILABLE; + break; + } else { + // already in unavailable state + return; + } + case STATE_DISABLED_AVAILABLE: + if (available) { + // already in available state + return; + } else { + mState = STATE_DISABLED_UNAVAILABLE; + break; + } + case STATE_ENABLED_AVAILABLE: + if (available) { + // already in available state + return; + } else { + mState = STATE_ENABLED_UNAVAILABLE; + break; + } + } + final int nbCallbacks = mStateCallbacks.beginBroadcast(); + for (int i = 0; i < nbCallbacks; i++) { + try { + mStateCallbacks.getBroadcastItem(i) + .dispatchSpatializerAvailableChanged(available); + } catch (RemoteException e) { + Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e); + } + } + mStateCallbacks.finishBroadcast(); + } + + //------------------------------------------------------ + // native Spatializer management + + /** + * precondition: mState == STATE_DISABLED_* + */ + private void createSpat() { + if (mSpat == null) { + mSpatCallback = new SpatializerCallback(); + mSpatHeadTrackingCallback = new SpatializerHeadTrackingCallback(); + mSpat = AudioSystem.getSpatializer(mSpatCallback); + try { + mSpat.setLevel((byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL); + //TODO: register heatracking callback only when sensors are registered + if (mSpat.isHeadTrackingSupported()) { + mSpat.registerHeadTrackingCallback(mSpatHeadTrackingCallback); + } + } catch (RemoteException e) { + Log.e(TAG, "Can't set spatializer level", e); + mState = STATE_NOT_SUPPORTED; + mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + } + } + } + + /** + * precondition: mState == STATE_ENABLED_* + */ + private void releaseSpat() { + if (mSpat != null) { + mSpatCallback = null; + try { + mSpat.registerHeadTrackingCallback(null); + mSpat.release(); + mSpat = null; + } catch (RemoteException e) { + Log.e(TAG, "Can't set release spatializer cleanly", e); + } + } + } + + //------------------------------------------------------ + // virtualization capabilities + synchronized boolean canBeSpatialized( + @NonNull AudioAttributes attributes, @NonNull AudioFormat format) { + logd("canBeSpatialized usage:" + attributes.getUsage() + + " format:" + format.toLogFriendlyString()); + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + logd("canBeSpatialized false due to state:" + mState); + return false; + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + break; + } + + // filter on AudioAttributes usage + switch (attributes.getUsage()) { + case AudioAttributes.USAGE_MEDIA: + case AudioAttributes.USAGE_GAME: + break; + default: + logd("canBeSpatialized false due to usage:" + attributes.getUsage()); + return false; + } + AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1]; + // going through adapter to take advantage of routing cache + mASA.getDevicesForAttributes(attributes).toArray(devices); + final boolean able = AudioSystem.canBeSpatialized(attributes, format, devices); + logd("canBeSpatialized returning " + able); + return able; + } + + //------------------------------------------------------ + // head tracking + final RemoteCallbackList<ISpatializerHeadTrackingModeCallback> mHeadTrackingModeCallbacks = + new RemoteCallbackList<ISpatializerHeadTrackingModeCallback>(); + + synchronized void registerHeadTrackingModeCallback( + @NonNull ISpatializerHeadTrackingModeCallback callback) { + mHeadTrackingModeCallbacks.register(callback); + } + + synchronized void unregisterHeadTrackingModeCallback( + @NonNull ISpatializerHeadTrackingModeCallback callback) { + mHeadTrackingModeCallbacks.unregister(callback); + } + + synchronized int[] getSupportedHeadTrackingModes() { + switch (mState) { + case STATE_UNINITIALIZED: + return new int[0]; + case STATE_NOT_SUPPORTED: + // return an empty list when Spatializer functionality is not supported + // because the list of head tracking modes you can set is actually empty + // as defined in {@link Spatializer#getSupportedHeadTrackingModes()} + return new int[0]; + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + if (mSpat == null) { + return new int[0]; + } + break; + } + // mSpat != null + try { + final byte[] values = mSpat.getSupportedHeadTrackingModes(); + ArrayList<Integer> list = new ArrayList<>(0); + for (byte value : values) { + switch (value) { + case SpatializerHeadTrackingMode.OTHER: + case SpatializerHeadTrackingMode.DISABLED: + // not expected here, skip + break; + case SpatializerHeadTrackingMode.RELATIVE_WORLD: + case SpatializerHeadTrackingMode.RELATIVE_SCREEN: + list.add(headTrackingModeTypeToSpatializerInt(value)); + break; + default: + Log.e(TAG, "Unexpected head tracking mode:" + value, + new IllegalArgumentException("invalid mode")); + break; + } + } + int[] modes = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + modes[i] = list.get(i); + } + return modes; + } catch (RemoteException e) { + Log.e(TAG, "Error calling getSupportedHeadTrackingModes", e); + return new int[] { Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED }; + } + } + + synchronized int getActualHeadTrackingMode() { + switch (mState) { + case STATE_UNINITIALIZED: + return Spatializer.HEAD_TRACKING_MODE_DISABLED; + case STATE_NOT_SUPPORTED: + return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + if (mSpat == null) { + return Spatializer.HEAD_TRACKING_MODE_DISABLED; + } + break; + } + // mSpat != null + try { + return headTrackingModeTypeToSpatializerInt(mSpat.getActualHeadTrackingMode()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling getActualHeadTrackingMode", e); + return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; + } + } + + synchronized int getDesiredHeadTrackingMode() { + return mDesiredHeadTrackingMode; + } + + synchronized void setGlobalTransform(@NonNull float[] transform) { + if (transform.length != 6) { + throw new IllegalArgumentException("invalid array size" + transform.length); + } + if (!checkSpatForHeadTracking("setGlobalTransform")) { + return; + } + try { + mSpat.setGlobalTransform(transform); + } catch (RemoteException e) { + Log.e(TAG, "Error calling setGlobalTransform", e); + } + } + + synchronized void recenterHeadTracker() { + if (!checkSpatForHeadTracking("recenterHeadTracker")) { + return; + } + try { + mSpat.recenterHeadTracker(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling recenterHeadTracker", e); + } + } + + synchronized void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) { + if (!checkSpatForHeadTracking("setDesiredHeadTrackingMode")) { + return; + } + try { + if (mode != mDesiredHeadTrackingMode) { + mSpat.setDesiredHeadTrackingMode(spatializerIntToHeadTrackingModeType(mode)); + mDesiredHeadTrackingMode = mode; + dispatchDesiredHeadTrackingMode(mode); + } + + } catch (RemoteException e) { + Log.e(TAG, "Error calling setDesiredHeadTrackingMode", e); + } + } + + private boolean checkSpatForHeadTracking(String funcName) { + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + return false; + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + if (mSpat == null) { + throw (new IllegalStateException( + "null Spatializer when calling " + funcName)); + } + break; + } + return true; + } + + private void dispatchActualHeadTrackingMode(int newMode) { + final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast(); + for (int i = 0; i < nbCallbacks; i++) { + try { + mHeadTrackingModeCallbacks.getBroadcastItem(i) + .dispatchSpatializerActualHeadTrackingModeChanged(newMode); + } catch (RemoteException e) { + Log.e(TAG, "Error in dispatchSpatializerActualHeadTrackingModeChanged", e); + } + } + mHeadTrackingModeCallbacks.finishBroadcast(); + } + + private void dispatchDesiredHeadTrackingMode(int newMode) { + final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast(); + for (int i = 0; i < nbCallbacks; i++) { + try { + mHeadTrackingModeCallbacks.getBroadcastItem(i) + .dispatchSpatializerDesiredHeadTrackingModeChanged(newMode); + } catch (RemoteException e) { + Log.e(TAG, "Error in dispatchSpatializerDesiredHeadTrackingModeChanged", e); + } + } + mHeadTrackingModeCallbacks.finishBroadcast(); + } + + //------------------------------------------------------ + // head pose + final RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback> mHeadPoseCallbacks = + new RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback>(); + + synchronized void registerHeadToSoundstagePoseCallback( + @NonNull ISpatializerHeadToSoundStagePoseCallback callback) { + mHeadPoseCallbacks.register(callback); + } + + synchronized void unregisterHeadToSoundstagePoseCallback( + @NonNull ISpatializerHeadToSoundStagePoseCallback callback) { + mHeadPoseCallbacks.unregister(callback); + } + + private void dispatchPoseUpdate(float[] pose) { + final int nbCallbacks = mHeadPoseCallbacks.beginBroadcast(); + for (int i = 0; i < nbCallbacks; i++) { + try { + mHeadPoseCallbacks.getBroadcastItem(i) + .dispatchPoseChanged(pose); + } catch (RemoteException e) { + Log.e(TAG, "Error in dispatchPoseChanged", e); + } + } + mHeadPoseCallbacks.finishBroadcast(); + } + + //------------------------------------------------------ + // vendor parameters + synchronized void setEffectParameter(int key, @NonNull byte[] value) { + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + throw (new IllegalStateException( + "Can't set parameter key:" + key + " without a spatializer")); + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + if (mSpat == null) { + throw (new IllegalStateException( + "null Spatializer for setParameter for key:" + key)); + } + break; + } + // mSpat != null + try { + mSpat.setParameter(key, value); + } catch (RemoteException e) { + Log.e(TAG, "Error in setParameter for key:" + key, e); + } + } + + synchronized void getEffectParameter(int key, @NonNull byte[] value) { + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + throw (new IllegalStateException( + "Can't get parameter key:" + key + " without a spatializer")); + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + if (mSpat == null) { + throw (new IllegalStateException( + "null Spatializer for getParameter for key:" + key)); + } + break; + } + // mSpat != null + try { + mSpat.getParameter(key, value); + } catch (RemoteException e) { + Log.e(TAG, "Error in getParameter for key:" + key, e); + } + } + + //------------------------------------------------------ + // output + + /** @see Spatializer#getOutput */ + synchronized int getOutput() { + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + throw (new IllegalStateException( + "Can't get output without a spatializer")); + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + if (mSpat == null) { + throw (new IllegalStateException( + "null Spatializer for getOutput")); + } + break; + } + // mSpat != null + try { + return mSpat.getOutput(); + } catch (RemoteException e) { + Log.e(TAG, "Error in getOutput", e); + return 0; + } + } + + final RemoteCallbackList<ISpatializerOutputCallback> mOutputCallbacks = + new RemoteCallbackList<ISpatializerOutputCallback>(); + + synchronized void registerSpatializerOutputCallback( + @NonNull ISpatializerOutputCallback callback) { + mOutputCallbacks.register(callback); + } + + synchronized void unregisterSpatializerOutputCallback( + @NonNull ISpatializerOutputCallback callback) { + mOutputCallbacks.unregister(callback); + } + + private void dispatchOutputUpdate(int output) { + final int nbCallbacks = mOutputCallbacks.beginBroadcast(); + for (int i = 0; i < nbCallbacks; i++) { + try { + mOutputCallbacks.getBroadcastItem(i).dispatchSpatializerOutputChanged(output); + } catch (RemoteException e) { + Log.e(TAG, "Error in dispatchOutputUpdate", e); + } + } + mOutputCallbacks.finishBroadcast(); + } + + //------------------------------------------------------ + // sensors + private void postInitSensors() { + mAudioService.postInitSpatializerHeadTrackingSensors(); + } + + synchronized void onInitSensors() { + final boolean init = (mSpatLevel != SpatializationLevel.NONE); + final String action = init ? "initializing" : "releasing"; + if (mSpat == null) { + Log.e(TAG, "not " + action + " sensors, null spatializer"); + return; + } + try { + if (!mSpat.isHeadTrackingSupported()) { + Log.e(TAG, "not " + action + " sensors, spatializer doesn't support headtracking"); + return; + } + } catch (RemoteException e) { + Log.e(TAG, "not " + action + " sensors, error querying headtracking", e); + return; + } + int headHandle = -1; + int screenHandle = -1; + if (init) { + if (mSensorManager == null) { + try { + mSensorManager = (SensorManager) + mAudioService.mContext.getSystemService(Context.SENSOR_SERVICE); + mDynSensorCallback = new HelperDynamicSensorCallback(); + mSensorManager.registerDynamicSensorCallback(mDynSensorCallback); + } catch (Exception e) { + Log.e(TAG, "Error with SensorManager, can't initialize sensors", e); + mSensorManager = null; + mDynSensorCallback = null; + return; + } + } + // initialize sensor handles + // TODO-HT update to non-private sensor once head tracker sensor is defined + for (Sensor sensor : mSensorManager.getDynamicSensorList( + Sensor.TYPE_DEVICE_PRIVATE_BASE)) { + if (sensor.getStringType().equals(HEADTRACKER_SENSOR)) { + headHandle = sensor.getHandle(); + break; + } + } + Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); + screenHandle = screenSensor.getHandle(); + } else { + if (mSensorManager != null && mDynSensorCallback != null) { + mSensorManager.unregisterDynamicSensorCallback(mDynSensorCallback); + mSensorManager = null; + mDynSensorCallback = null; + } + // -1 is disable value for both screen and head tracker handles + } + try { + Log.i(TAG, "setScreenSensor:" + screenHandle); + mSpat.setScreenSensor(screenHandle); + } catch (Exception e) { + Log.e(TAG, "Error calling setScreenSensor:" + screenHandle, e); + } + try { + Log.i(TAG, "setHeadSensor:" + headHandle); + mSpat.setHeadSensor(headHandle); + } catch (Exception e) { + Log.e(TAG, "Error calling setHeadSensor:" + headHandle, e); + } + } + + //------------------------------------------------------ + // SDK <-> AIDL converters + private static int headTrackingModeTypeToSpatializerInt(byte mode) { + switch (mode) { + case SpatializerHeadTrackingMode.OTHER: + return Spatializer.HEAD_TRACKING_MODE_OTHER; + case SpatializerHeadTrackingMode.DISABLED: + return Spatializer.HEAD_TRACKING_MODE_DISABLED; + case SpatializerHeadTrackingMode.RELATIVE_WORLD: + return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; + case SpatializerHeadTrackingMode.RELATIVE_SCREEN: + return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE; + default: + throw(new IllegalArgumentException("Unexpected head tracking mode:" + mode)); + } + } + + private static byte spatializerIntToHeadTrackingModeType(int sdkMode) { + switch (sdkMode) { + case Spatializer.HEAD_TRACKING_MODE_OTHER: + return SpatializerHeadTrackingMode.OTHER; + case Spatializer.HEAD_TRACKING_MODE_DISABLED: + return SpatializerHeadTrackingMode.DISABLED; + case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD: + return SpatializerHeadTrackingMode.RELATIVE_WORLD; + case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE: + return SpatializerHeadTrackingMode.RELATIVE_SCREEN; + default: + throw(new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode)); + } + } + + private static int spatializationLevelToSpatializerInt(byte level) { + switch (level) { + case SpatializationLevel.NONE: + return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + case SpatializationLevel.SPATIALIZER_MULTICHANNEL: + return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL; + case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS: + return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS; + default: + throw(new IllegalArgumentException("Unexpected spatializer level:" + level)); + } + } +} diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index b42f8980d1c0..9c8ccd946b7f 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -47,6 +47,7 @@ import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.PromptInfo; +import android.hardware.biometrics.SensorLocationInternal; import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.face.FaceSensorProperties; import android.hardware.face.FaceSensorPropertiesInternal; @@ -766,8 +767,9 @@ public class AuthService extends SystemService { if (isUdfps && udfpsProps.length == 3) { return new FingerprintSensorPropertiesInternal(sensorId, Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser, - componentInfo, sensorType, resetLockoutRequiresHardwareAuthToken, udfpsProps[0], - udfpsProps[1], udfpsProps[2]); + componentInfo, sensorType, resetLockoutRequiresHardwareAuthToken, + List.of(new SensorLocationInternal("" /* display */, + udfpsProps[0], udfpsProps[1], udfpsProps[2]))); } else { return new FingerprintSensorPropertiesInternal(sensorId, Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser, diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index e0775d48b42f..758cf7a7d430 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -1036,7 +1036,8 @@ public class BiometricService extends SystemService { promptInfo.setAuthenticators(authenticators); return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors, - userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */); + userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */, + getContext()); } /** @@ -1375,15 +1376,20 @@ public class BiometricService extends SystemService { try { final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo, - opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists()); + opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(), + getContext()); final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus(); Slog.d(TAG, "handleAuthenticate: modality(" + preAuthStatus.first + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo - + " requestId: " + requestId); - - if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) { + + " requestId: " + requestId + " promptInfo.isIgnoreEnrollmentState: " + + promptInfo.isIgnoreEnrollmentState()); + // BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can + // be shown for this case. + if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS + || preAuthStatus.second + == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) { // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but // CREDENTIAL is requested and available, set the bundle to only request // CREDENTIAL. diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index cd0ff10168bb..05c3f68f355b 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -26,6 +26,8 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustManager; +import android.content.Context; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.PromptInfo; @@ -59,6 +61,7 @@ class PreAuthInfo { static final int CREDENTIAL_NOT_ENROLLED = 9; static final int BIOMETRIC_LOCKOUT_TIMED = 10; static final int BIOMETRIC_LOCKOUT_PERMANENT = 11; + static final int BIOMETRIC_SENSOR_PRIVACY_ENABLED = 12; @IntDef({AUTHENTICATOR_OK, BIOMETRIC_NO_HARDWARE, BIOMETRIC_DISABLED_BY_DEVICE_POLICY, @@ -69,7 +72,8 @@ class PreAuthInfo { BIOMETRIC_NOT_ENABLED_FOR_APPS, CREDENTIAL_NOT_ENROLLED, BIOMETRIC_LOCKOUT_TIMED, - BIOMETRIC_LOCKOUT_PERMANENT}) + BIOMETRIC_LOCKOUT_PERMANENT, + BIOMETRIC_SENSOR_PRIVACY_ENABLED}) @Retention(RetentionPolicy.SOURCE) @interface AuthenticatorStatus {} @@ -83,13 +87,16 @@ class PreAuthInfo { final List<Pair<BiometricSensor, Integer>> ineligibleSensors; final boolean credentialAvailable; final boolean confirmationRequested; + final boolean ignoreEnrollmentState; + final int userId; + final Context context; static PreAuthInfo create(ITrustManager trustManager, DevicePolicyManager devicePolicyManager, BiometricService.SettingObserver settingObserver, List<BiometricSensor> sensors, int userId, PromptInfo promptInfo, String opPackageName, - boolean checkDevicePolicyManager) + boolean checkDevicePolicyManager, Context context) throws RemoteException { final boolean confirmationRequested = promptInfo.isConfirmationRequested(); @@ -114,14 +121,23 @@ class PreAuthInfo { @AuthenticatorStatus int status = getStatusForBiometricAuthenticator( devicePolicyManager, settingObserver, sensor, userId, opPackageName, checkDevicePolicyManager, requestedStrength, - promptInfo.getAllowedSensorIds()); + promptInfo.getAllowedSensorIds(), + promptInfo.isIgnoreEnrollmentState(), + context); Slog.d(TAG, "Package: " + opPackageName + " Sensor ID: " + sensor.id + " Modality: " + sensor.modality + " Status: " + status); - if (status == AUTHENTICATOR_OK) { + // A sensor with privacy enabled will still be eligible to + // authenticate with biometric prompt. This is so the framework can display + // a sensor privacy error message to users after briefly showing the + // Biometric Prompt. + // + // Note: if only a certain sensor is required and the privacy is enabled, + // canAuthenticate() will return false. + if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) { eligibleSensors.add(sensor); } else { ineligibleSensors.add(new Pair<>(sensor, status)); @@ -130,7 +146,8 @@ class PreAuthInfo { } return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, - eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested); + eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested, + promptInfo.isIgnoreEnrollmentState(), userId, context); } /** @@ -145,7 +162,8 @@ class PreAuthInfo { BiometricService.SettingObserver settingObserver, BiometricSensor sensor, int userId, String opPackageName, boolean checkDevicePolicyManager, int requestedStrength, - @NonNull List<Integer> requestedSensorIds) { + @NonNull List<Integer> requestedSensorIds, + boolean ignoreEnrollmentState, Context context) { if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) { return BIOMETRIC_NO_HARDWARE; @@ -167,9 +185,20 @@ class PreAuthInfo { return BIOMETRIC_HARDWARE_NOT_DETECTED; } - if (!sensor.impl.hasEnrolledTemplates(userId, opPackageName)) { + if (!sensor.impl.hasEnrolledTemplates(userId, opPackageName) + && !ignoreEnrollmentState) { return BIOMETRIC_NOT_ENROLLED; } + final SensorPrivacyManager sensorPrivacyManager = context + .getSystemService(SensorPrivacyManager.class); + + if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) { + if (sensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) { + return BIOMETRIC_SENSOR_PRIVACY_ENABLED; + } + } + final @LockoutTracker.LockoutMode int lockoutMode = sensor.impl.getLockoutModeForUser(userId); @@ -238,7 +267,8 @@ class PreAuthInfo { private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested, boolean credentialRequested, List<BiometricSensor> eligibleSensors, List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable, - boolean confirmationRequested) { + boolean confirmationRequested, boolean ignoreEnrollmentState, int userId, + Context context) { mBiometricRequested = biometricRequested; mBiometricStrengthRequested = biometricStrengthRequested; this.credentialRequested = credentialRequested; @@ -247,6 +277,9 @@ class PreAuthInfo { this.ineligibleSensors = ineligibleSensors; this.credentialAvailable = credentialAvailable; this.confirmationRequested = confirmationRequested; + this.ignoreEnrollmentState = ignoreEnrollmentState; + this.userId = userId; + this.context = context; } private Pair<BiometricSensor, Integer> calculateErrorByPriority() { @@ -274,15 +307,35 @@ class PreAuthInfo { private Pair<Integer, Integer> getInternalStatus() { @AuthenticatorStatus final int status; @BiometricAuthenticator.Modality int modality = TYPE_NONE; + + final SensorPrivacyManager sensorPrivacyManager = context + .getSystemService(SensorPrivacyManager.class); + + boolean cameraPrivacyEnabled = false; + if (sensorPrivacyManager != null) { + cameraPrivacyEnabled = sensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId); + } + if (mBiometricRequested && credentialRequested) { if (credentialAvailable || !eligibleSensors.isEmpty()) { - status = AUTHENTICATOR_OK; - if (credentialAvailable) { - modality |= TYPE_CREDENTIAL; - } for (BiometricSensor sensor : eligibleSensors) { modality |= sensor.modality; } + + if (credentialAvailable) { + modality |= TYPE_CREDENTIAL; + status = AUTHENTICATOR_OK; + } else if (modality == TYPE_FACE && cameraPrivacyEnabled) { + // If the only modality requested is face, credential is unavailable, + // and the face sensor privacy is enabled then return + // BIOMETRIC_SENSOR_PRIVACY_ENABLED. + // + // Note: This sensor will still be eligible for calls to authenticate. + status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; + } else { + status = AUTHENTICATOR_OK; + } } else { // Pick the first sensor error if it exists if (!ineligibleSensors.isEmpty()) { @@ -296,10 +349,18 @@ class PreAuthInfo { } } else if (mBiometricRequested) { if (!eligibleSensors.isEmpty()) { - status = AUTHENTICATOR_OK; - for (BiometricSensor sensor : eligibleSensors) { - modality |= sensor.modality; - } + for (BiometricSensor sensor : eligibleSensors) { + modality |= sensor.modality; + } + if (modality == TYPE_FACE && cameraPrivacyEnabled) { + // If the only modality requested is face and the privacy is enabled + // then return BIOMETRIC_SENSOR_PRIVACY_ENABLED. + // + // Note: This sensor will still be eligible for calls to authenticate. + status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; + } else { + status = AUTHENTICATOR_OK; + } } else { // Pick the first sensor error if it exists if (!ineligibleSensors.isEmpty()) { @@ -320,9 +381,9 @@ class PreAuthInfo { Slog.e(TAG, "No authenticators requested"); status = BIOMETRIC_NO_HARDWARE; } - Slog.d(TAG, "getCanAuthenticateInternal Modality: " + modality + " AuthenticatorStatus: " + status); + return new Pair<>(modality, status); } @@ -356,6 +417,7 @@ class PreAuthInfo { case CREDENTIAL_NOT_ENROLLED: case BIOMETRIC_LOCKOUT_TIMED: case BIOMETRIC_LOCKOUT_PERMANENT: + case BIOMETRIC_SENSOR_PRIVACY_ENABLED: break; case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 996f0fd3a55f..0e2582c23b86 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -33,6 +33,7 @@ import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_LOCKOUT_TIMED; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENABLED_FOR_APPS; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE; +import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_SENSOR_PRIVACY_ENABLED; import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED; import android.annotation.NonNull; @@ -50,7 +51,6 @@ import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorProperties; import android.hardware.biometrics.SensorPropertiesInternal; -import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Binder; import android.os.Build; import android.os.RemoteException; @@ -62,7 +62,6 @@ import android.util.Slog; import com.android.internal.R; import com.android.internal.widget.LockPatternUtils; -import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BaseClientMonitor; import java.util.List; @@ -280,6 +279,9 @@ public class Utils { case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT: biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS; break; + case BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED: + biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; + break; default: Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode); biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; @@ -339,7 +341,8 @@ public class Utils { case BIOMETRIC_LOCKOUT_PERMANENT: return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; - + case BIOMETRIC_SENSOR_PRIVACY_ENABLED: + return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED; case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: case BIOMETRIC_HARDWARE_NOT_DETECTED: case BIOMETRIC_NOT_ENABLED_FOR_APPS: @@ -541,14 +544,4 @@ public class Utils { throw new IllegalArgumentException("Unknown strength: " + strength); } } - - public static int getUdfpsAuthReason(@NonNull AuthenticationClient<?> client) { - if (client.isKeyguard()) { - return IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD; - } else if (client.isBiometricPrompt()) { - return IUdfpsOverlayController.REASON_AUTH_BP; - } else { - return IUdfpsOverlayController.REASON_AUTH_FPM_OTHER; - } - } } diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 6f38ed04cd96..358263df916b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -28,6 +28,7 @@ import android.content.pm.ApplicationInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.BiometricOverlayConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.os.RemoteException; @@ -167,6 +168,10 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> return Utils.isKeyguard(getContext(), getOwnerString()); } + private boolean isSettings() { + return Utils.isSettings(getContext(), getOwnerString()); + } + @Override protected boolean isCryptoOperation() { return mOperationId != 0; @@ -246,7 +251,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> "Successful background authentication!"); } - mAlreadyDone = true; + markAlreadyDone(); if (mTaskStackListener != null) { mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); @@ -322,7 +327,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> final @LockoutTracker.LockoutMode int lockoutMode = handleFailedAttempt(getTargetUserId()); if (lockoutMode != LockoutTracker.LOCKOUT_NONE) { - mAlreadyDone = true; + markAlreadyDone(); } final CoexCoordinator coordinator = CoexCoordinator.getInstance(); @@ -359,6 +364,43 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> } } + /** + * Only call this method on interfaces where lockout does not come from onError, I.E. the + * old HIDL implementation. + */ + protected void onLockoutTimed(long durationMillis) { + final ClientMonitorCallbackConverter listener = getListener(); + final CoexCoordinator coordinator = CoexCoordinator.getInstance(); + coordinator.onAuthenticationError(this, BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, + new CoexCoordinator.ErrorCallback() { + @Override + public void sendHapticFeedback() { + if (listener != null && mShouldVibrate) { + vibrateError(); + } + } + }); + } + + /** + * Only call this method on interfaces where lockout does not come from onError, I.E. the + * old HIDL implementation. + */ + protected void onLockoutPermanent() { + final ClientMonitorCallbackConverter listener = getListener(); + final CoexCoordinator coordinator = CoexCoordinator.getInstance(); + coordinator.onAuthenticationError(this, + BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT, + new CoexCoordinator.ErrorCallback() { + @Override + public void sendHapticFeedback() { + if (listener != null && mShouldVibrate) { + vibrateError(); + } + } + }); + } + private void sendCancelOnly(@Nullable ClientMonitorCallbackConverter listener) { if (listener == null) { Slog.e(TAG, "Unable to sendAuthenticationCanceled, listener null"); @@ -457,4 +499,20 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> public boolean wasAuthAttempted() { return mAuthAttempted; } + + protected int getShowOverlayReason() { + if (isKeyguard()) { + return BiometricOverlayConstants.REASON_AUTH_KEYGUARD; + } else if (isBiometricPrompt()) { + // BP reason always takes precedent over settings, since callers from within + // settings can always invoke BP. + return BiometricOverlayConstants.REASON_AUTH_BP; + } else if (isSettings()) { + // This is pretty much only for FingerprintManager#authenticate usage from + // FingerprintSettings. + return BiometricOverlayConstants.REASON_AUTH_SETTINGS; + } else { + return BiometricOverlayConstants.REASON_AUTH_OTHER; + } + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java index 9764a167fbbf..b73e91173a43 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java @@ -114,7 +114,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor // Currently only used for authentication client. The cookie generated by BiometricService // is never 0. private final int mCookie; - boolean mAlreadyDone; + private boolean mAlreadyDone = false; // Use an empty callback by default since delayed operations can receive events // before they are started and cause NPE in subclasses that access this field directly. @@ -202,11 +202,9 @@ public abstract class BaseClientMonitor extends LoggableMonitor return callback; } - public boolean isAlreadyDone() { - return mAlreadyDone; - } - - public void destroy() { + /** Signals this operation has completed its lifecycle and should no longer be used. */ + void destroy() { + mAlreadyDone = true; if (mToken != null) { try { mToken.unlinkToDeath(this, 0); @@ -218,6 +216,20 @@ public abstract class BaseClientMonitor extends LoggableMonitor } } + /** + * Call while the operation is still active, but nearly done, to prevent any action + * upon client death (only needed for authentication clients). + */ + void markAlreadyDone() { + Slog.d(TAG, "marking operation as done: " + this); + mAlreadyDone = true; + } + + /** If this operation has been marked as completely done (or cancelled). */ + public boolean isAlreadyDone() { + return mAlreadyDone; + } + @Override public void binderDied() { binderDiedInternal(true /* clearListener */); @@ -225,10 +237,9 @@ public abstract class BaseClientMonitor extends LoggableMonitor // TODO(b/157790417): Move this to the scheduler void binderDiedInternal(boolean clearListener) { - Slog.e(TAG, "Binder died, owner: " + getOwnerString() - + ", operation: " + this.getClass().getName()); + Slog.e(TAG, "Binder died, operation: " + this); - if (isAlreadyDone()) { + if (mAlreadyDone) { Slog.w(TAG, "Binder died but client is finished, ignoring"); return; } @@ -299,7 +310,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor @Override public String toString() { return "{[" + mSequentialId + "] " - + this.getClass().getSimpleName() + + this.getClass().getName() + ", proto=" + getProtoEnum() + ", owner=" + getOwnerString() + ", cookie=" + getCookie() diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 361ec40f2877..a358bc2bad55 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -605,6 +605,9 @@ public class BiometricScheduler { if (operation.mState == Operation.STATE_WAITING_FOR_COOKIE) { Slog.w(getTag(), "Skipping cancellation for non-started operation: " + operation); // We can set it to null immediately, since the HAL was never notified to start. + if (mCurrentOperation != null) { + mCurrentOperation.mClientMonitor.destroy(); + } mCurrentOperation = null; startNextOperationIfIdle(); return; diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java index 9191b8b55989..2826e0c97305 100644 --- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java @@ -19,7 +19,9 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricOverlayConstants; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.fingerprint.FingerprintManager; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; @@ -128,4 +130,15 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En public boolean interruptsPrecedingClients() { return true; } + + protected int getOverlayReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) { + switch (reason) { + case FingerprintManager.ENROLL_FIND_SENSOR: + return BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR; + case FingerprintManager.ENROLL_ENROLL: + return BiometricOverlayConstants.REASON_ENROLL_ENROLLING; + default: + return BiometricOverlayConstants.REASON_UNKNOWN; + } + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java new file mode 100644 index 000000000000..008717899aba --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2021 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.biometrics.sensors; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.fingerprint.ISidefpsController; +import android.hardware.fingerprint.IUdfpsOverlayController; +import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; +import android.os.RemoteException; +import android.util.Slog; + +import java.util.Optional; +import java.util.function.Consumer; + +/** + * Single entry point & holder for controllers managing UI overlays for biometrics. + * + * For common operations, like {@link #show(int, int, AcquisitionClient)}, modalities are + * skipped if they are not present (provided as null via the constructor). + * + * Use the getters, such as {@link #ifUdfps(OverlayControllerConsumer)}, to get a controller for + * operations that are unique to a single modality. + */ +public final class SensorOverlays { + + private static final String TAG = "SensorOverlays"; + + @NonNull private final Optional<IUdfpsOverlayController> mUdfpsOverlayController; + @NonNull private final Optional<ISidefpsController> mSidefpsController; + + /** + * Create an overlay controller for each modality. + * + * @param udfpsOverlayController under display fps or null if not present on device + * @param sidefpsController side fps or null if not present on device + */ + public SensorOverlays( + @Nullable IUdfpsOverlayController udfpsOverlayController, + @Nullable ISidefpsController sidefpsController) { + mUdfpsOverlayController = Optional.ofNullable(udfpsOverlayController); + mSidefpsController = Optional.ofNullable(sidefpsController); + } + + /** + * Show the overlay. + * + * @param sensorId sensor id + * @param reason reason for showing + * @param client client performing operation + */ + public void show(int sensorId, @BiometricOverlayConstants.ShowReason int reason, + @NonNull AcquisitionClient<?> client) { + if (mSidefpsController.isPresent()) { + try { + mSidefpsController.get().show(sensorId, reason); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when showing the side-fps overlay", e); + } + } + + if (mUdfpsOverlayController.isPresent()) { + final IUdfpsOverlayControllerCallback callback = + new IUdfpsOverlayControllerCallback.Stub() { + @Override + public void onUserCanceled() { + client.onUserCanceled(); + } + }; + + try { + mUdfpsOverlayController.get().showUdfpsOverlay(sensorId, reason, callback); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e); + } + } + } + + /** + * Hide the overlay. + * + * @param sensorId sensor id + */ + public void hide(int sensorId) { + if (mSidefpsController.isPresent()) { + try { + mSidefpsController.get().hide(sensorId); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when hiding the side-fps overlay", e); + } + } + + if (mUdfpsOverlayController.isPresent()) { + try { + mUdfpsOverlayController.get().hideUdfpsOverlay(sensorId); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e); + } + } + } + + /** + * Use the udfps controller, if present. + * @param consumer action + */ + public void ifUdfps(OverlayControllerConsumer<IUdfpsOverlayController> consumer) { + if (mUdfpsOverlayController.isPresent()) { + try { + consumer.accept(mUdfpsOverlayController.get()); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception using overlay controller", e); + } + } + } + + /** + * Consumer for a biometric overlay controller. + * + * This behaves like a normal {@link Consumer} except that it will trap and log + * any thrown {@link RemoteException}. + * + * @param <T> the type of the input to the operation + **/ + @FunctionalInterface + public interface OverlayControllerConsumer<T> { + /** Perform the operation. */ + void accept(T t) throws RemoteException; + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index cbceba6cc959..4131ae127ab2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.NotificationManager; import android.content.Context; import android.content.res.Resources; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; @@ -56,6 +57,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @NonNull private final LockoutCache mLockoutCache; @Nullable private final NotificationManager mNotificationManager; @Nullable private ICancellationSignal mCancellationSignal; + @Nullable private SensorPrivacyManager mSensorPrivacyManager; private final int[] mBiometricPromptIgnoreList; private final int[] mBiometricPromptIgnoreListVendor; @@ -81,6 +83,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements mUsageStats = usageStats; mLockoutCache = lockoutCache; mNotificationManager = context.getSystemService(NotificationManager.class); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); final Resources resources = getContext().getResources(); mBiometricPromptIgnoreList = resources.getIntArray( @@ -108,7 +111,16 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @Override protected void startHalOperation() { try { - mCancellationSignal = getFreshDaemon().authenticate(mOperationId); + if (mSensorPrivacyManager != null + && mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, + getTargetUserId())) { + onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + } else { + mCancellationSignal = getFreshDaemon().authenticate(mOperationId); + } } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting auth", e); onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); @@ -225,6 +237,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @Override public void onLockoutTimed(long durationMillis) { + super.onLockoutTimed(durationMillis); mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED); // Lockout metrics are logged as an error code. final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT; @@ -239,6 +252,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @Override public void onLockoutPermanent() { + super.onLockoutPermanent(); mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT); // Lockout metrics are logged as an error code. final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java index 2ef0911658b1..2158dfe7bde5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java @@ -19,6 +19,8 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.hardware.SensorPrivacyManager; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.face.ISession; @@ -41,6 +43,7 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det private final boolean mIsStrongBiometric; @Nullable private ICancellationSignal mCancellationSignal; + @Nullable private SensorPrivacyManager mSensorPrivacyManager; public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, long requestId, @@ -51,6 +54,7 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient); setRequestId(requestId); mIsStrongBiometric = isStrongBiometric; + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); } @Override @@ -73,6 +77,14 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det @Override protected void startHalOperation() { + if (mSensorPrivacyManager != null + && mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) { + onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + return; + } + try { mCancellationSignal = getFreshDaemon().detectInteraction(); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java index c364dbb4d615..2b5f49546d69 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java @@ -22,6 +22,7 @@ import android.content.Context; import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.ISession; import android.hardware.biometrics.face.ISessionCallback; +import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; @@ -52,6 +53,7 @@ public class FaceStartUserClient extends StartUserClient<IFace, ISession> { try { final ISession newSession = getFreshDaemon().createSession(getSensorId(), getTargetUserId(), mSessionCallback); + Binder.allowBlocking(newSession.asBinder()); mUserStartedCallback.onUserStarted(getTargetUserId(), newSession); getCallback().onClientFinished(this, true /* success */); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java index 40f2801541d3..7548d2871a15 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java @@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; @@ -55,6 +56,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { private final int[] mKeyguardIgnoreListVendor; private int mLastAcquire; + private SensorPrivacyManager mSensorPrivacyManager; FaceAuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @@ -71,6 +73,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { isKeyguardBypassEnabled); setRequestId(requestId); mUsageStats = usageStats; + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); final Resources resources = getContext().getResources(); mBiometricPromptIgnoreList = resources.getIntArray( @@ -97,6 +100,15 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { @Override protected void startHalOperation() { + + if (mSensorPrivacyManager != null + && mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) { + onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + return; + } + try { getFreshDaemon().authenticate(mOperationId); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index f35bb7ffd26b..c5d33ed7400b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -281,7 +281,7 @@ public class FingerprintService extends SystemService { @Override // Binder call public long authenticate(final IBinder token, final long operationId, final int sensorId, final int userId, final IFingerprintServiceReceiver receiver, - final String opPackageName) { + final String opPackageName, boolean ignoreEnrollmentState) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int callingUserId = UserHandle.getCallingUserId(); @@ -333,7 +333,8 @@ public class FingerprintService extends SystemService { && sensorProps != null && sensorProps.isAnyUdfpsType()) { identity = Binder.clearCallingIdentity(); try { - return authenticateWithPrompt(operationId, sensorProps, userId, receiver); + return authenticateWithPrompt(operationId, sensorProps, userId, receiver, + ignoreEnrollmentState); } finally { Binder.restoreCallingIdentity(identity); } @@ -347,7 +348,8 @@ public class FingerprintService extends SystemService { final long operationId, @NonNull final FingerprintSensorPropertiesInternal props, final int userId, - final IFingerprintServiceReceiver receiver) { + final IFingerprintServiceReceiver receiver, + boolean ignoreEnrollmentState) { final Context context = getUiContext(); final Executor executor = context.getMainExecutor(); @@ -368,6 +370,7 @@ public class FingerprintService extends SystemService { }) .setAllowedSensorIds(new ArrayList<>( Collections.singletonList(props.sensorId))) + .setIgnoreEnrollmentState(ignoreEnrollmentState) .build(); final BiometricPrompt.AuthenticationCallback promptCallback = diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java index 0050a895034f..be0e6edb2a42 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java @@ -23,7 +23,6 @@ import static android.hardware.fingerprint.FingerprintStateListener.STATE_IDLE; import static android.hardware.fingerprint.FingerprintStateListener.STATE_KEYGUARD_AUTH; import android.annotation.NonNull; -import android.content.Context; import android.hardware.fingerprint.FingerprintStateListener; import android.hardware.fingerprint.IFingerprintStateListener; import android.os.RemoteException; @@ -34,8 +33,6 @@ import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.EnrollClient; import com.android.server.biometrics.sensors.EnrollmentModifier; -import com.android.server.biometrics.sensors.RemovalConsumer; -import com.android.server.biometrics.sensors.fingerprint.hidl.FingerprintEnrollClient; import java.util.concurrent.CopyOnWriteArrayList; @@ -70,7 +67,7 @@ public class FingerprintStateCallback implements BaseClientMonitor.Callback { } else { mFingerprintState = STATE_AUTH_OTHER; } - } else if (client instanceof FingerprintEnrollClient) { + } else if (client instanceof EnrollClient) { mFingerprintState = STATE_ENROLLING; } else { Slog.w(FingerprintService.TAG, @@ -143,6 +140,7 @@ public class FingerprintStateCallback implements BaseClientMonitor.Callback { /** * Enables clients to register a FingerprintStateListener. Used by FingerprintService to forward * updates in fingerprint sensor state to the SideFpNsEventHandler + * * @param listener */ public void registerFingerprintStateListener(@NonNull IFingerprintStateListener listener) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/SidefpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/SidefpsHelper.java deleted file mode 100644 index 474066c227d2..000000000000 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/SidefpsHelper.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2020 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.biometrics.sensors.fingerprint; - -import android.annotation.Nullable; -import android.hardware.fingerprint.ISidefpsController; -import android.os.RemoteException; -import android.util.Slog; - -/** - * Contains helper methods for side-fps fingerprint controller. - */ -public class SidefpsHelper { - private static final String TAG = "SidefpsHelper"; - - /** - * Shows the side-fps affordance - * @param sidefpsController controller that shows and hides the side-fps affordance - */ - public static void showOverlay(@Nullable ISidefpsController sidefpsController) { - if (sidefpsController == null) { - return; - } - - try { - sidefpsController.show(); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when showing the side-fps overlay", e); - } - } - - /** - * Hides the side-fps affordance - * @param sidefpsController controller that shows and hides the side-fps affordance - */ - public static void hideOverlay(@Nullable ISidefpsController sidefpsController) { - if (sidefpsController == null) { - return; - } - try { - sidefpsController.hide(); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when hiding the side-fps overlay", e); - } - } -} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java index 879c8a0317d7..29661d46f328 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java @@ -17,17 +17,12 @@ package com.android.server.biometrics.sensors.fingerprint; import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.FingerprintManager; -import android.hardware.fingerprint.IUdfpsOverlayController; -import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; import android.os.RemoteException; import android.util.Slog; -import com.android.server.biometrics.sensors.AcquisitionClient; - /** * Contains helper methods for under-display fingerprint HIDL. */ @@ -68,88 +63,6 @@ public class UdfpsHelper { } } - public static int getReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) { - switch (reason) { - case FingerprintManager.ENROLL_FIND_SENSOR: - return IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR; - case FingerprintManager.ENROLL_ENROLL: - return IUdfpsOverlayController.REASON_ENROLL_ENROLLING; - default: - return IUdfpsOverlayController.REASON_UNKNOWN; - } - } - - public static void showUdfpsOverlay(int sensorId, int reason, - @Nullable IUdfpsOverlayController udfpsOverlayController, - @NonNull AcquisitionClient<?> client) { - if (udfpsOverlayController == null) { - return; - } - - final IUdfpsOverlayControllerCallback callback = - new IUdfpsOverlayControllerCallback.Stub() { - @Override - public void onUserCanceled() { - client.onUserCanceled(); - } - }; - - try { - udfpsOverlayController.showUdfpsOverlay(sensorId, reason, callback); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e); - } - } - - public static void hideUdfpsOverlay(int sensorId, - @Nullable IUdfpsOverlayController udfpsOverlayController) { - if (udfpsOverlayController == null) { - return; - } - try { - udfpsOverlayController.hideUdfpsOverlay(sensorId); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e); - } - } - - public static void onAcquiredGood(int sensorId, - @Nullable IUdfpsOverlayController udfpsOverlayController) { - if (udfpsOverlayController == null) { - return; - } - - try { - udfpsOverlayController.onAcquiredGood(sensorId); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when sending onAcquiredGood", e); - } - } - - public static void onEnrollmentProgress(int sensorId, int remaining, - @Nullable IUdfpsOverlayController udfpsOverlayController) { - if (udfpsOverlayController == null) { - return; - } - try { - udfpsOverlayController.onEnrollmentProgress(sensorId, remaining); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when sending onEnrollmentProgress", e); - } - } - - public static void onEnrollmentHelp(int sensorId, - @Nullable IUdfpsOverlayController udfpsOverlayController) { - if (udfpsOverlayController == null) { - return; - } - try { - udfpsOverlayController.onEnrollmentHelp(sensorId); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when sending onEnrollmentHelp", e); - } - } - public static boolean isValidAcquisitionMessage(@NonNull Context context, int acquireInfo, int vendorCode) { return FingerprintManager.getAcquiredString(context, acquireInfo, vendorCode) != null; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index b405be75bdf1..ca051e9e9bf4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -27,20 +27,20 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; -import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.LockoutTracker; +import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.Udfps; -import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; import java.util.ArrayList; @@ -53,7 +53,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp private static final String TAG = "FingerprintAuthenticationClient"; @NonNull private final LockoutCache mLockoutCache; - @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; + @NonNull private final SensorOverlays mSensorOverlays; @NonNull private final FingerprintSensorPropertiesInternal mSensorProps; @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback; @@ -68,6 +68,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp int sensorId, boolean isStrongBiometric, int statsClient, @Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache, @Nullable IUdfpsOverlayController udfpsOverlayController, + @Nullable ISidefpsController sidefpsController, boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps) { super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner, @@ -77,7 +78,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp false /* isKeyguardBypassEnabled */); setRequestId(requestId); mLockoutCache = lockoutCache; - mUdfpsOverlayController = udfpsOverlayController; + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mSensorProps = sensorProps; mALSProbeCallback = createALSCallback(false /* startWithClient */); } @@ -120,7 +121,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp if (authenticated) { mState = STATE_STOPPED; - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); } else { mState = STATE_STARTED_PAUSED_ATTEMPTED; } @@ -131,7 +132,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp // For UDFPS, notify SysUI that the illumination can be turned off. // See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD) { - UdfpsHelper.onAcquiredGood(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.ifUdfps(controller -> controller.onAcquiredGood(getSensorId())); } super.onAcquired(acquiredInfo, vendorCode); @@ -145,27 +146,27 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp BiometricNotificationUtils.showBadCalibrationNotification(getContext()); } - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); } @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(getSensorId(), Utils.getUdfpsAuthReason(this), - mUdfpsOverlayController, this); + mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this); + try { mCancellationSignal = getFreshDaemon().authenticate(mOperationId); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } } @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); if (mCancellationSignal != null) { try { mCancellationSignal.cancel(); @@ -239,7 +240,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp Slog.e(TAG, "Remote exception", e); } - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } @@ -256,7 +257,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp Slog.e(TAG, "Remote exception", e); } - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index da91cdd981b9..ac3ce896049b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.hardware.biometrics.BiometricOverlayConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.fingerprint.ISession; @@ -31,7 +32,7 @@ import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.DetectionConsumer; -import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; +import com.android.server.biometrics.sensors.SensorOverlays; /** * Performs fingerprint detection without exposing any matching information (e.g. accept/reject @@ -42,8 +43,7 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> implements Det private static final String TAG = "FingerprintDetectClient"; private final boolean mIsStrongBiometric; - @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; - + @NonNull private final SensorOverlays mSensorOverlays; @Nullable private ICancellationSignal mCancellationSignal; FingerprintDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, @@ -57,7 +57,7 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> implements Det BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient); setRequestId(requestId); mIsStrongBiometric = isStrongBiometric; - mUdfpsOverlayController = udfpsOverlayController; + mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/); } @Override @@ -68,7 +68,8 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> implements Det @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); + try { mCancellationSignal.cancel(); } catch (RemoteException e) { @@ -79,14 +80,13 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> implements Det @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(getSensorId(), - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, - mUdfpsOverlayController, this); + mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this); + try { mCancellationSignal = getFreshDaemon().detectInteraction(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting finger detect", e); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index c420c5c57241..ccb34aad3198 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -39,8 +39,8 @@ import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.EnrollClient; +import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; -import com.android.server.biometrics.sensors.fingerprint.SidefpsHelper; import com.android.server.biometrics.sensors.fingerprint.Udfps; import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; @@ -49,8 +49,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { private static final String TAG = "FingerprintEnrollClient"; @NonNull private final FingerprintSensorPropertiesInternal mSensorProps; - @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; - @Nullable private final ISidefpsController mSidefpsController; + @NonNull private final SensorOverlays mSensorOverlays; private final @FingerprintManager.EnrollReason int mEnrollReason; @Nullable private ICancellationSignal mCancellationSignal; @@ -63,7 +62,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, @NonNull FingerprintSensorPropertiesInternal sensorProps, - @Nullable IUdfpsOverlayController udfpsOvelayController, + @Nullable IUdfpsOverlayController udfpsOverlayController, @Nullable ISidefpsController sidefpsController, int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) { // UDFPS haptics occur when an image is acquired (instead of when the result is known) @@ -71,8 +70,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { 0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId, !sensorProps.isAnyUdfpsType() /* shouldVibrate */); mSensorProps = sensorProps; - mUdfpsOverlayController = udfpsOvelayController; - mSidefpsController = sidefpsController; + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mMaxTemplatesPerUser = maxTemplatesPerUser; mEnrollReason = enrollReason; @@ -91,11 +89,11 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) { super.onEnrollResult(identifier, remaining); - UdfpsHelper.onEnrollmentProgress(getSensorId(), remaining, mUdfpsOverlayController); + mSensorOverlays.ifUdfps( + controller -> controller.onEnrollmentProgress(getSensorId(), remaining)); if (remaining == 0) { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - SidefpsHelper.hideOverlay(mSidefpsController); + mSensorOverlays.hide(getSensorId()); } } @@ -106,12 +104,14 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD && mSensorProps.isAnyUdfpsType()) { vibrateSuccess(); - UdfpsHelper.onAcquiredGood(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.ifUdfps(controller -> controller.onAcquiredGood(getSensorId())); } - if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) { - UdfpsHelper.onEnrollmentHelp(getSensorId(), mUdfpsOverlayController); - } + mSensorOverlays.ifUdfps(controller -> { + if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) { + controller.onEnrollmentHelp(getSensorId()); + } + }); super.onAcquired(acquiredInfo, vendorCode); } @@ -120,8 +120,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { public void onError(int errorCode, int vendorCode) { super.onError(errorCode, vendorCode); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - SidefpsHelper.hideOverlay(mSidefpsController); + mSensorOverlays.hide(getSensorId()); } @Override @@ -133,8 +132,8 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - SidefpsHelper.hideOverlay(mSidefpsController); + mSensorOverlays.hide(getSensorId()); + if (mCancellationSignal != null) { try { mCancellationSignal.cancel(); @@ -149,10 +148,8 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(getSensorId(), - UdfpsHelper.getReasonFromEnrollReason(mEnrollReason), - mUdfpsOverlayController, this); - SidefpsHelper.showOverlay(mSidefpsController); + mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this); + BiometricNotificationUtils.cancelBadCalibrationNotification(getContext()); try { mCancellationSignal = getFreshDaemon().enroll( diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index ca83dda3bc4e..0defc3fb6a50 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -25,10 +25,12 @@ import android.app.ActivityTaskManager; import android.app.TaskStackListener; import android.content.Context; import android.content.pm.UserInfo; +import android.content.res.TypedArray; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; +import android.hardware.biometrics.SensorLocationInternal; import android.hardware.biometrics.common.ComponentInfo; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.SensorProps; @@ -145,6 +147,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mActivityTaskManager = ActivityTaskManager.getInstance(); mTaskStackListener = new BiometricTaskStackListener(); + final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context); + for (SensorProps prop : props) { final int sensorId = prop.commonProps.sensorId; @@ -164,9 +168,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi componentInfo, prop.sensorType, true /* resetLockoutRequiresHardwareAuthToken */, - prop.sensorLocations[0].sensorLocationX, - prop.sensorLocations[0].sensorLocationY, - prop.sensorLocations[0].sensorRadius); + !workaroundLocations.isEmpty() ? workaroundLocations : + List.of(new SensorLocationInternal( + "" /* displayId */, + prop.sensorLocations[0].sensorLocationX, + prop.sensorLocations[0].sensorLocationY, + prop.sensorLocations[0].sensorRadius))); final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler, internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher); @@ -403,7 +410,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi userId, operationId, restricted, opPackageName, cookie, false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient, mTaskStackListener, mSensors.get(sensorId).getLockoutCache(), - mUdfpsOverlayController, allowBackgroundAuthentication, + mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication, mSensors.get(sensorId).getSensorProperties()); scheduleForSensor(sensorId, client, mFingerprintStateCallback); }); @@ -647,4 +654,45 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi void setTestHalEnabled(boolean enabled) { mTestHalEnabled = enabled; } + + // TODO(b/174868353): workaround for gaps in HAL interface (remove and get directly from HAL) + // reads values via an overlay instead of querying the HAL + @NonNull + private List<SensorLocationInternal> getWorkaroundSensorProps(@NonNull Context context) { + final List<SensorLocationInternal> sensorLocations = new ArrayList<>(); + + final TypedArray sfpsProps = context.getResources().obtainTypedArray( + com.android.internal.R.array.config_sfps_sensor_props); + for (int i = 0; i < sfpsProps.length(); i++) { + final int id = sfpsProps.getResourceId(i, -1); + if (id > 0) { + final SensorLocationInternal location = parseSensorLocation( + context.getResources().obtainTypedArray(id)); + if (location != null) { + sensorLocations.add(location); + } + } + } + sfpsProps.recycle(); + + return sensorLocations; + } + + @Nullable + private SensorLocationInternal parseSensorLocation(@Nullable TypedArray array) { + if (array == null) { + return null; + } + + try { + return new SensorLocationInternal( + array.getString(0), + array.getInt(1, 0), + array.getInt(2, 0), + array.getInt(3, 0)); + } catch (Exception e) { + Slog.w(getTag(), "malformed sensor location", e); + } + return null; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java index 2d40c91cbc75..ee81620fdf77 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java @@ -22,6 +22,7 @@ import android.content.Context; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.ISessionCallback; +import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; @@ -53,6 +54,7 @@ public class FingerprintStartUserClient extends StartUserClient<IFingerprint, IS try { final ISession newSession = getFreshDaemon().createSession(getSensorId(), getTargetUserId(), mSessionCallback); + Binder.allowBlocking(newSession.asBinder()); mUserStartedCallback.onUserStarted(getTargetUserId(), newSession); getCallback().onClientFinished(this, true /* success */); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index d2882aa4094c..5f2f4cf6ef3c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -629,7 +629,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mContext, mLazyDaemon, token, requestId, listener, userId, operationId, restricted, opPackageName, cookie, false /* requireConfirmation */, mSensorProperties.sensorId, isStrongBiometric, statsClient, - mTaskStackListener, mLockoutTracker, mUdfpsOverlayController, + mTaskStackListener, mLockoutTracker, + mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication, mSensorProperties); mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback); }); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index 79ad8e1a5c70..dd68b4d37e2a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -426,8 +426,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage mSensorProperties = new FingerprintSensorPropertiesInternal(sensorProps.sensorId, sensorProps.sensorStrength, maxTemplatesAllowed, sensorProps.componentInfo, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, - resetLockoutRequiresHardwareAuthToken, sensorProps.sensorLocationX, - sensorProps.sensorLocationY, sensorProps.sensorRadius); + resetLockoutRequiresHardwareAuthToken, sensorProps.getAllLocations()); mMockHalResultController = controller; mUserHasTrust = new SparseBooleanArray(); mTrustManager = context.getSystemService(TrustManager.class); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java index 7d95ec098fee..3058e2508f5f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java @@ -26,16 +26,17 @@ import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; -import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutTracker; +import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.Udfps; import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; @@ -52,7 +53,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi private static final String TAG = "Biometrics/FingerprintAuthClient"; private final LockoutFrameworkImpl mLockoutFrameworkImpl; - @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; + @NonNull private final SensorOverlays mSensorOverlays; @NonNull private final FingerprintSensorPropertiesInternal mSensorProps; @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback; @@ -67,6 +68,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi @NonNull TaskStackListener taskStackListener, @NonNull LockoutFrameworkImpl lockoutTracker, @Nullable IUdfpsOverlayController udfpsOverlayController, + @Nullable ISidefpsController sidefpsController, boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps) { super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, @@ -76,7 +78,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi false /* isKeyguardBypassEnabled */); setRequestId(requestId); mLockoutFrameworkImpl = lockoutTracker; - mUdfpsOverlayController = udfpsOverlayController; + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mSensorProps = sensorProps; mALSProbeCallback = createALSCallback(false /* startWithClient */); } @@ -112,7 +114,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi if (authenticated) { mState = STATE_STOPPED; resetFailedAttempts(getTargetUserId()); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); } else { mState = STATE_STARTED_PAUSED_ATTEMPTED; final @LockoutTracker.LockoutMode int lockoutMode = @@ -125,7 +127,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi // Send the error, but do not invoke the FinishCallback yet. Since lockout is not // controlled by the HAL, the framework must stop the sensor before finishing the // client. - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */); cancel(); } @@ -140,7 +142,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi BiometricNotificationUtils.showBadCalibrationNotification(getContext()); } - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); } private void resetFailedAttempts(int userId) { @@ -168,8 +170,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(getSensorId(), Utils.getUdfpsAuthReason(this), - mUdfpsOverlayController, this); + mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this); + try { // GroupId was never used. In fact, groupId is always the same as userId. getFreshDaemon().authenticate(mOperationId, getTargetUserId()); @@ -177,14 +179,15 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi Slog.e(TAG, "Remote exception when requesting auth", e); onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } } @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); + try { getFreshDaemon().cancel(); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java index 147a20699b54..b854fb300ece 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; +import android.hardware.biometrics.BiometricOverlayConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.IUdfpsOverlayController; @@ -33,6 +34,7 @@ import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.PerformanceTracker; +import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.Udfps; import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; @@ -48,7 +50,7 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> private static final String TAG = "FingerprintDetectClient"; private final boolean mIsStrongBiometric; - @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; + @NonNull private final SensorOverlays mSensorOverlays; private boolean mIsPointerDown; public FingerprintDetectClient(@NonNull Context context, @@ -61,13 +63,14 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT, BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient); setRequestId(requestId); - mUdfpsOverlayController = udfpsOverlayController; + mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */); mIsStrongBiometric = isStrongBiometric; } @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); + try { getFreshDaemon().cancel(); } catch (RemoteException e) { @@ -86,16 +89,15 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(getSensorId(), - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, - mUdfpsOverlayController, this); + mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this); + try { getFreshDaemon().authenticate(0 /* operationId */, getTargetUserId()); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting auth", e); onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java index dc705346f534..1ebf44ca707f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java @@ -35,7 +35,7 @@ import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.EnrollClient; -import com.android.server.biometrics.sensors.fingerprint.SidefpsHelper; +import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.Udfps; import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; @@ -49,8 +49,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint private static final String TAG = "FingerprintEnrollClient"; - @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; - @Nullable private final ISidefpsController mSidefpsController; + @NonNull private final SensorOverlays mSensorOverlays; private final @FingerprintManager.EnrollReason int mEnrollReason; private boolean mIsPointerDown; @@ -65,8 +64,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId, true /* shouldVibrate */); - mUdfpsOverlayController = udfpsOverlayController; - mSidefpsController = sidefpsController; + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mEnrollReason = enrollReason; if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) { @@ -95,10 +93,8 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(getSensorId(), - UdfpsHelper.getReasonFromEnrollReason(mEnrollReason), - mUdfpsOverlayController, this); - SidefpsHelper.showOverlay(mSidefpsController); + mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this); + BiometricNotificationUtils.cancelBadCalibrationNotification(getContext()); try { // GroupId was never used. In fact, groupId is always the same as userId. @@ -107,16 +103,15 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint Slog.e(TAG, "Remote exception when requesting enroll", e); onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - SidefpsHelper.hideOverlay(mSidefpsController); + mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } } @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - SidefpsHelper.hideOverlay(mSidefpsController); + mSensorOverlays.hide(getSensorId()); + try { getFreshDaemon().cancel(); } catch (RemoteException e) { @@ -131,11 +126,11 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) { super.onEnrollResult(identifier, remaining); - UdfpsHelper.onEnrollmentProgress(getSensorId(), remaining, mUdfpsOverlayController); + mSensorOverlays.ifUdfps( + controller -> controller.onEnrollmentProgress(getSensorId(), remaining)); if (remaining == 0) { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - SidefpsHelper.hideOverlay(mSidefpsController); + mSensorOverlays.hide(getSensorId()); } } @@ -143,17 +138,18 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint public void onAcquired(int acquiredInfo, int vendorCode) { super.onAcquired(acquiredInfo, vendorCode); - if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) { - UdfpsHelper.onEnrollmentHelp(getSensorId(), mUdfpsOverlayController); - } + mSensorOverlays.ifUdfps(controller -> { + if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) { + controller.onEnrollmentHelp(getSensorId()); + } + }); } @Override public void onError(int errorCode, int vendorCode) { super.onError(errorCode, vendorCode); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - SidefpsHelper.hideOverlay(mSidefpsController); + mSensorOverlays.hide(getSensorId()); } @Override diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java index 54a4ad42fd99..23f0ffbbf0b8 100644 --- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java @@ -52,11 +52,11 @@ public class BroadcastRadioService extends SystemService { public BroadcastRadioService(Context context) { super(context); - mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService(); + mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService(mLock); mV1Modules = mHal1.loadModules(); OptionalInt max = mV1Modules.stream().mapToInt(RadioManager.ModuleProperties::getId).max(); mHal2 = new com.android.server.broadcastradio.hal2.BroadcastRadioService( - max.isPresent() ? max.getAsInt() + 1 : 0); + max.isPresent() ? max.getAsInt() + 1 : 0, mLock); } @Override @@ -111,7 +111,7 @@ public class BroadcastRadioService extends SystemService { synchronized (mLock) { if (!mHal2.hasAnyModules()) { Slog.i(TAG, "There are no HAL 2.x modules registered"); - return new AnnouncementAggregator(listener); + return new AnnouncementAggregator(listener, mLock); } return mHal2.addAnnouncementListener(enabledTypes, listener); diff --git a/services/core/java/com/android/server/broadcastradio/OWNERS b/services/core/java/com/android/server/broadcastradio/OWNERS index ea4421eae96a..3e360e7e992c 100644 --- a/services/core/java/com/android/server/broadcastradio/OWNERS +++ b/services/core/java/com/android/server/broadcastradio/OWNERS @@ -1,2 +1,3 @@ +keunyoung@google.com +oscarazu@google.com twasilczyk@google.com -randolphs@google.com diff --git a/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java index e8ac5477469b..5da60328cd70 100644 --- a/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java @@ -17,16 +17,9 @@ package com.android.server.broadcastradio.hal1; import android.annotation.NonNull; -import android.Manifest; -import android.content.Context; -import android.content.pm.PackageManager; -import android.hardware.radio.IRadioService; import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; -import android.os.ParcelableException; - -import com.android.server.SystemService; import java.util.List; import java.util.Objects; @@ -37,7 +30,7 @@ public class BroadcastRadioService { */ private final long mNativeContext = nativeInit(); - private final Object mLock = new Object(); + private final Object mLock; @Override protected void finalize() throws Throwable { @@ -51,6 +44,14 @@ public class BroadcastRadioService { private native Tuner nativeOpenTuner(long nativeContext, int moduleId, RadioManager.BandConfig config, boolean withAudio, ITunerCallback callback); + /** + * Constructor. should pass + * {@code com.android.server.broadcastradio.BroadcastRadioService#mLock} for lock. + */ + public BroadcastRadioService(@NonNull Object lock) { + mLock = lock; + } + public @NonNull List<RadioManager.ModuleProperties> loadModules() { synchronized (mLock) { return Objects.requireNonNull(nativeLoadModules(mNativeContext)); diff --git a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java index 53076975849b..42e296f3e4ec 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java @@ -35,7 +35,7 @@ import java.util.Objects; public class AnnouncementAggregator extends ICloseHandle.Stub { private static final String TAG = "BcRadio2Srv.AnnAggr"; - private final Object mLock = new Object(); + private final Object mLock; @NonNull private final IAnnouncementListener mListener; private final IBinder.DeathRecipient mDeathRecipient = new DeathRecipient(); @@ -45,8 +45,9 @@ public class AnnouncementAggregator extends ICloseHandle.Stub { @GuardedBy("mLock") private boolean mIsClosed = false; - public AnnouncementAggregator(@NonNull IAnnouncementListener listener) { + public AnnouncementAggregator(@NonNull IAnnouncementListener listener, @NonNull Object lock) { mListener = Objects.requireNonNull(listener); + mLock = Objects.requireNonNull(lock); try { listener.asBinder().linkToDeath(mDeathRecipient, 0); } catch (RemoteException ex) { diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java index 5e79c5943d7b..5c07f76e5011 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java @@ -42,7 +42,7 @@ import java.util.stream.Collectors; public class BroadcastRadioService { private static final String TAG = "BcRadio2Srv"; - private final Object mLock = new Object(); + private final Object mLock; @GuardedBy("mLock") private int mNextModuleId = 0; @@ -68,7 +68,7 @@ public class BroadcastRadioService { moduleId = mNextModuleId; } - RadioModule module = RadioModule.tryLoadingModule(moduleId, serviceName); + RadioModule module = RadioModule.tryLoadingModule(moduleId, serviceName, mLock); if (module == null) { return; } @@ -116,8 +116,9 @@ public class BroadcastRadioService { } }; - public BroadcastRadioService(int nextModuleId) { + public BroadcastRadioService(int nextModuleId, Object lock) { mNextModuleId = nextModuleId; + mLock = lock; try { IServiceManager manager = IServiceManager.getService(); if (manager == null) { @@ -174,7 +175,7 @@ public class BroadcastRadioService { public ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes, @NonNull IAnnouncementListener listener) { - AnnouncementAggregator aggregator = new AnnouncementAggregator(listener); + AnnouncementAggregator aggregator = new AnnouncementAggregator(listener, mLock); boolean anySupported = false; synchronized (mLock) { for (RadioModule module : mModules.values()) { diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java index b7e188c73eab..ef7f4c9fc919 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java @@ -58,7 +58,7 @@ class RadioModule { @NonNull private final IBroadcastRadio mService; @NonNull public final RadioManager.ModuleProperties mProperties; - private final Object mLock = new Object(); + private final Object mLock; @NonNull private final Handler mHandler; @GuardedBy("mLock") @@ -132,13 +132,15 @@ class RadioModule { @VisibleForTesting RadioModule(@NonNull IBroadcastRadio service, - @NonNull RadioManager.ModuleProperties properties) { + @NonNull RadioManager.ModuleProperties properties, @NonNull Object lock) { mProperties = Objects.requireNonNull(properties); mService = Objects.requireNonNull(service); + mLock = Objects.requireNonNull(lock); mHandler = new Handler(Looper.getMainLooper()); } - public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName) { + public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName, + Object lock) { try { IBroadcastRadio service = IBroadcastRadio.getService(fqName); if (service == null) return null; @@ -156,7 +158,7 @@ class RadioModule { RadioManager.ModuleProperties prop = Convert.propertiesFromHal(idx, fqName, service.getProperties(), amfmConfig.value, dabConfig.value); - return new RadioModule(service, prop); + return new RadioModule(service, prop, lock); } catch (RemoteException ex) { Slog.e(TAG, "failed to load module " + fqName, ex); return null; @@ -178,7 +180,8 @@ class RadioModule { }); mHalTunerSession = Objects.requireNonNull(hwSession.value); } - TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb); + TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb, + mLock); mAidlTunerSessions.add(tunerSession); // Propagate state to new client. Note: These callbacks are invoked while holding mLock @@ -377,7 +380,7 @@ class RadioModule { } }; - synchronized (mService) { + synchronized (mLock) { mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHnd) -> { halResult.value = result; hwCloseHandle.value = closeHnd; @@ -401,7 +404,7 @@ class RadioModule { if (id == 0) throw new IllegalArgumentException("Image ID is missing"); byte[] rawImage; - synchronized (mService) { + synchronized (mLock) { List<Byte> rawList = Utils.maybeRethrow(() -> mService.getImage(id)); rawImage = new byte[rawList.size()]; for (int i = 0; i < rawList.size(); i++) { diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java index 7ab3bdd859e4..200af2fb1da7 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java @@ -40,7 +40,7 @@ class TunerSession extends ITuner.Stub { private static final String TAG = "BcRadio2Srv.session"; private static final String kAudioDeviceName = "Radio tuner source"; - private final Object mLock = new Object(); + private final Object mLock; private final RadioModule mModule; private final ITunerSession mHwSession; @@ -53,10 +53,12 @@ class TunerSession extends ITuner.Stub { private RadioManager.BandConfig mDummyConfig = null; TunerSession(@NonNull RadioModule module, @NonNull ITunerSession hwSession, - @NonNull android.hardware.radio.ITunerCallback callback) { + @NonNull android.hardware.radio.ITunerCallback callback, + @NonNull Object lock) { mModule = Objects.requireNonNull(module); mHwSession = Objects.requireNonNull(hwSession); mCallback = Objects.requireNonNull(callback); + mLock = Objects.requireNonNull(lock); } @Override diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index c3ba80094305..3120dc58eebd 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -15,37 +15,49 @@ */ package com.android.server.camera; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.os.Build.VERSION_CODES.M; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.app.ActivityManager; import android.app.ActivityTaskManager; -import android.app.TaskStackListener; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.Disabled; +import android.compat.annotation.Overridable; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.hardware.CameraSessionStats; import android.hardware.CameraStreamStats; import android.hardware.ICameraService; import android.hardware.ICameraServiceProxy; +import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.CaptureRequest; +import android.hardware.devicestate.DeviceStateManager; +import android.hardware.devicestate.DeviceStateManager.FoldStateListener; import android.hardware.display.DisplayManager; import android.media.AudioManager; import android.nfc.INfcAdapter; import android.os.Binder; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; import android.os.UserManager; import android.stats.camera.nano.CameraProtos.CameraStreamProto; import android.util.ArrayMap; @@ -57,8 +69,8 @@ import android.view.IDisplayWindowListener; import android.view.Surface; import android.view.WindowManagerGlobal; -import com.android.internal.annotations.GuardedBy; import com.android.framework.protobuf.nano.MessageNano; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.ServiceThread; @@ -72,7 +84,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -94,6 +105,57 @@ public class CameraServiceProxy extends SystemService public static final String CAMERA_SERVICE_PROXY_BINDER_NAME = "media.camera.proxy"; + /** + * When enabled this change id forces the packages it is applied to override the default + * camera rotate & crop behavior and always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE . + * The default behavior along with all possible override combinations is discussed in the table + * below. + */ + @ChangeId + @Overridable + @Disabled + @TestApi + public static final long OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS = 189229956L; // buganizer id + + /** + * When enabled this change id forces the packages it is applied to ignore the current value of + * 'android:resizeableActivity' as well as target SDK equal to or below M and consider the + * activity as non-resizeable. In this case, the value of camera rotate & crop will only depend + * on the needed compensation considering the current display rotation. + */ + @ChangeId + @Overridable + @Disabled + @TestApi + public static final long OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK = 191513214L; // buganizer id + + /** + * Possible override combinations + * + * |OVERRIDE |OVERRIDE_ + * |CAMERA_ |CAMERA_ + * |ROTATE_ |RESIZEABLE_ + * |AND_CROP_ |AND_SDK_ + * |DEFAULTS |CHECK + * _________________________________________________ + * Default Behavior | D |D + * _________________________________________________ + * Ignore SDK&Resize | D |E + * _________________________________________________ + * SCALER_ROTATE_AND_CROP_NONE | E |D, E + * _________________________________________________ + * Where: + * E -> Override enabled + * D -> Override disabled + * Default behavior -> Rotate&crop will be calculated depending on the required + * compensation necessary for the current display rotation. + * Additionally the app must either target M (or below) + * or is declared as non-resizeable. + * Ignore SDK&Resize -> The Rotate&crop value will depend on the required + * compensation for the current display rotation. + * SCALER_ROTATE_AND_CROP_NONE -> Always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE + */ + // Flags arguments to NFC adapter to enable/disable NFC public static final int DISABLE_POLLING_FLAGS = 0x1000; public static final int ENABLE_POLLING_FLAGS = 0x0000; @@ -246,62 +308,15 @@ public class CameraServiceProxy extends SystemService private final DisplayWindowListener mDisplayWindowListener = new DisplayWindowListener(); - private final TaskStateHandler mTaskStackListener = new TaskStateHandler(); - - private final class TaskInfo { - private int frontTaskId; - private boolean isResizeable; - private boolean isFixedOrientationLandscape; - private boolean isFixedOrientationPortrait; - private int displayId; + public static final class TaskInfo { + public int frontTaskId; + public boolean isResizeable; + public boolean isFixedOrientationLandscape; + public boolean isFixedOrientationPortrait; + public int displayId; + public int userId; } - private final class TaskStateHandler extends TaskStackListener { - private final Object mMapLock = new Object(); - - // maps the package name to its corresponding current top level task id - @GuardedBy("mMapLock") - private final ArrayMap<String, TaskInfo> mTaskInfoMap = new ArrayMap<>(); - - @Override - public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { - synchronized (mMapLock) { - TaskInfo info = new TaskInfo(); - info.frontTaskId = taskInfo.taskId; - info.isResizeable = taskInfo.isResizeable; - info.displayId = taskInfo.displayId; - info.isFixedOrientationLandscape = ActivityInfo.isFixedOrientationLandscape( - taskInfo.topActivityInfo.screenOrientation); - info.isFixedOrientationPortrait = ActivityInfo.isFixedOrientationPortrait( - taskInfo.topActivityInfo.screenOrientation); - mTaskInfoMap.put(taskInfo.topActivityInfo.packageName, info); - } - } - - @Override - public void onTaskRemoved(int taskId) { - synchronized (mMapLock) { - for (Map.Entry<String, TaskInfo> entry : mTaskInfoMap.entrySet()){ - if (entry.getValue().frontTaskId == taskId) { - mTaskInfoMap.remove(entry.getKey()); - break; - } - } - } - } - - public @Nullable TaskInfo getFrontTaskInfo(String packageName) { - synchronized (mMapLock) { - if (mTaskInfoMap.containsKey(packageName)) { - return mTaskInfoMap.get(packageName); - } - } - - Log.e(TAG, "Top task with package name: " + packageName + " not found!"); - return null; - } - }; - private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -327,93 +342,181 @@ public class CameraServiceProxy extends SystemService } }; - private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() { - private boolean isMOrBelow(Context ctx, String packageName) { - try { - return ctx.getPackageManager().getPackageInfo( - packageName, 0).applicationInfo.targetSdkVersion <= M; - } catch (PackageManager.NameNotFoundException e) { - Slog.e(TAG,"Package name not found!"); - } - return false; + private static boolean isMOrBelow(Context ctx, String packageName) { + try { + return ctx.getPackageManager().getPackageInfo( + packageName, 0).applicationInfo.targetSdkVersion <= M; + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG,"Package name not found!"); } + return false; + } - /** - * Gets whether crop-rotate-scale is needed. - */ - private boolean getNeedCropRotateScale(@NonNull Context ctx, @NonNull String packageName, - @Nullable TaskInfo taskInfo, int sensorOrientation, int lensFacing) { - if (taskInfo == null) { - return false; - } + /** + * Estimate the app crop-rotate-scale compensation value. + */ + public static int getCropRotateScale(@NonNull Context ctx, @NonNull String packageName, + @Nullable TaskInfo taskInfo, int displayRotation, int lensFacing, + boolean ignoreResizableAndSdkCheck) { + if (taskInfo == null) { + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } - // External cameras do not need crop-rotate-scale. - if (lensFacing != CameraMetadata.LENS_FACING_FRONT - && lensFacing != CameraMetadata.LENS_FACING_BACK) { - Log.v(TAG, "lensFacing=" + lensFacing + ". Crop-rotate-scale is disabled."); - return false; - } + // External cameras do not need crop-rotate-scale. + if (lensFacing != CameraMetadata.LENS_FACING_FRONT + && lensFacing != CameraMetadata.LENS_FACING_BACK) { + Log.v(TAG, "lensFacing=" + lensFacing + ". Crop-rotate-scale is disabled."); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } - // Only enable the crop-rotate-scale workaround if the app targets M or below and is not - // resizeable. - if (!isMOrBelow(ctx, packageName) && taskInfo.isResizeable) { - Slog.v(TAG, - "The activity is N or above and claims to support resizeable-activity. " - + "Crop-rotate-scale is disabled."); - return false; - } + // In case the activity behavior is not explicitly overridden, enable the + // crop-rotate-scale workaround if the app targets M (or below) or is not + // resizeable. + if (!ignoreResizableAndSdkCheck && !isMOrBelow(ctx, packageName) && + taskInfo.isResizeable) { + Slog.v(TAG, + "The activity is N or above and claims to support resizeable-activity. " + + "Crop-rotate-scale is disabled."); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } - DisplayManager displayManager = ctx.getSystemService(DisplayManager.class); - Display display = displayManager.getDisplay(taskInfo.displayId); - int rotation = display.getRotation(); - int rotationDegree = 0; - switch (rotation) { - case Surface.ROTATION_0: - rotationDegree = 0; - break; - case Surface.ROTATION_90: - rotationDegree = 90; - break; - case Surface.ROTATION_180: - rotationDegree = 180; - break; - case Surface.ROTATION_270: - rotationDegree = 270; - break; - } + if (!taskInfo.isFixedOrientationPortrait && !taskInfo.isFixedOrientationLandscape) { + Log.v(TAG, "Non-fixed orientation activity. Crop-rotate-scale is disabled."); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } - // Here we only need to know whether the camera is landscape or portrait. Therefore we - // don't need to consider whether it is a front or back camera. The formula works for - // both. - boolean landscapeCamera = ((rotationDegree + sensorOrientation) % 180 == 0); - Slog.v(TAG, - "Display.getRotation()=" + rotationDegree - + " CameraCharacteristics.SENSOR_ORIENTATION=" + sensorOrientation - + " isFixedOrientationPortrait=" + taskInfo.isFixedOrientationPortrait - + " isFixedOrientationLandscape=" + - taskInfo.isFixedOrientationLandscape); - // We need to do crop-rotate-scale when camera is landscape and activity is portrait or - // vice versa. - return (taskInfo.isFixedOrientationPortrait && landscapeCamera) - || (taskInfo.isFixedOrientationLandscape && !landscapeCamera); + int rotationDegree; + switch (displayRotation) { + case Surface.ROTATION_0: + rotationDegree = 0; + break; + case Surface.ROTATION_90: + rotationDegree = 90; + break; + case Surface.ROTATION_180: + rotationDegree = 180; + break; + case Surface.ROTATION_270: + rotationDegree = 270; + break; + default: + Log.e(TAG, "Unsupported display rotation: " + displayRotation); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + + Slog.v(TAG, + "Display.getRotation()=" + rotationDegree + + " isFixedOrientationPortrait=" + taskInfo.isFixedOrientationPortrait + + " isFixedOrientationLandscape=" + + taskInfo.isFixedOrientationLandscape); + // We are trying to estimate the necessary rotation compensation for clients that + // don't handle various display orientations. + // The logic that is missing on client side is similar to the reference code + // in {@link android.hardware.Camera#setDisplayOrientation} where "info.orientation" + // is already applied in "CameraUtils::getRotationTransform". + // Care should be taken to reverse the rotation direction depending on the camera + // lens facing. + if (rotationDegree == 0) { + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + if (lensFacing == CameraCharacteristics.LENS_FACING_FRONT) { + // Switch direction for front facing cameras + rotationDegree = 360 - rotationDegree; } + switch (rotationDegree) { + case 90: + return CaptureRequest.SCALER_ROTATE_AND_CROP_90; + case 270: + return CaptureRequest.SCALER_ROTATE_AND_CROP_270; + case 180: + return CaptureRequest.SCALER_ROTATE_AND_CROP_180; + case 0: + default: + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + } + + private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() { @Override - public boolean isRotateAndCropOverrideNeeded(String packageName, int sensorOrientation, - int lensFacing) { + public int getRotateAndCropOverride(String packageName, int lensFacing, int userId) { if (Binder.getCallingUid() != Process.CAMERASERVER_UID) { Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " + " camera service UID!"); - return false; + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + + TaskInfo taskInfo = null; + ParceledListSlice<ActivityManager.RecentTaskInfo> recentTasks = null; + + try { + recentTasks = ActivityTaskManager.getService().getRecentTasks(/*maxNum*/1, + /*flags*/ 0, userId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to query recent tasks!"); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + + if ((recentTasks != null) && (!recentTasks.getList().isEmpty())) { + ActivityManager.RecentTaskInfo task = recentTasks.getList().get(0); + if (packageName.equals(task.topActivityInfo.packageName)) { + taskInfo = new TaskInfo(); + taskInfo.frontTaskId = task.taskId; + taskInfo.isResizeable = + (task.topActivityInfo.resizeMode != RESIZE_MODE_UNRESIZEABLE); + taskInfo.displayId = task.displayId; + taskInfo.userId = task.userId; + taskInfo.isFixedOrientationLandscape = + ActivityInfo.isFixedOrientationLandscape( + task.topActivityInfo.screenOrientation); + taskInfo.isFixedOrientationPortrait = + ActivityInfo.isFixedOrientationPortrait( + task.topActivityInfo.screenOrientation); + } else { + Log.e(TAG, "Recent task package name: " + task.topActivityInfo.packageName + + " doesn't match with camera client package name: " + packageName); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + } else { + Log.e(TAG, "Recent task list is empty!"); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; } // TODO: Modify the sensor orientation in camera characteristics along with any 3A // regions in capture requests/results to account for thea physical rotation. The // former is somewhat tricky as it assumes that camera clients always check for the // current value by retrieving the camera characteristics from the camera device. - return getNeedCropRotateScale(mContext, packageName, - mTaskStackListener.getFrontTaskInfo(packageName), sensorOrientation, - lensFacing); + if ((taskInfo != null) && (CompatChanges.isChangeEnabled( + OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS, packageName, + UserHandle.getUserHandleForUid(taskInfo.userId)))) { + Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS enabled!"); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + boolean ignoreResizableAndSdkCheck = false; + if ((taskInfo != null) && (CompatChanges.isChangeEnabled( + OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK, packageName, + UserHandle.getUserHandleForUid(taskInfo.userId)))) { + Slog.v(TAG, "OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK enabled!"); + ignoreResizableAndSdkCheck = true; + } + + DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); + int displayRotation; + if (displayManager != null) { + Display display = displayManager.getDisplay(taskInfo.displayId); + if (display == null) { + Slog.e(TAG, "Invalid display id: " + taskInfo.displayId); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + + displayRotation = display.getRotation(); + } else { + Slog.e(TAG, "Failed to query display manager!"); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + + return getCropRotateScale(mContext, packageName, taskInfo, displayRotation, + lensFacing, ignoreResizableAndSdkCheck); } @Override @@ -447,6 +550,8 @@ public class CameraServiceProxy extends SystemService } }; + private final FoldStateListener mFoldStateListener; + public CameraServiceProxy(Context context) { super(context); mContext = context; @@ -459,6 +564,14 @@ public class CameraServiceProxy extends SystemService // Don't keep any extra logging threads if not needed mLogWriterService.setKeepAliveTime(1, TimeUnit.SECONDS); mLogWriterService.allowCoreThreadTimeOut(true); + + mFoldStateListener = new FoldStateListener(mContext, folded -> { + if (folded) { + setDeviceStateFlags(ICameraService.DEVICE_STATE_FOLDED); + } else { + clearDeviceStateFlags(ICameraService.DEVICE_STATE_FOLDED); + } + }); } /** @@ -471,7 +584,7 @@ public class CameraServiceProxy extends SystemService * * @see #clearDeviceStateFlags(int) */ - public void setDeviceStateFlags(@DeviceStateFlags int deviceStateFlags) { + private void setDeviceStateFlags(@DeviceStateFlags int deviceStateFlags) { synchronized (mLock) { mHandler.removeMessages(MSG_NOTIFY_DEVICE_STATE); mDeviceState |= deviceStateFlags; @@ -491,7 +604,7 @@ public class CameraServiceProxy extends SystemService * * @see #setDeviceStateFlags(int) */ - public void clearDeviceStateFlags(@DeviceStateFlags int deviceStateFlags) { + private void clearDeviceStateFlags(@DeviceStateFlags int deviceStateFlags) { synchronized (mLock) { mHandler.removeMessages(MSG_NOTIFY_DEVICE_STATE); mDeviceState &= ~deviceStateFlags; @@ -544,12 +657,6 @@ public class CameraServiceProxy extends SystemService CameraStatsJobService.schedule(mContext); try { - ActivityTaskManager.getService().registerTaskStackListener(mTaskStackListener); - } catch (RemoteException e) { - Log.e(TAG, "Failed to register task stack listener!"); - } - - try { int[] displayIds = WindowManagerGlobal.getWindowManagerService() .registerDisplayWindowListener(mDisplayWindowListener); for (int i = 0; i < displayIds.length; i++) { @@ -558,6 +665,9 @@ public class CameraServiceProxy extends SystemService } catch (RemoteException e) { Log.e(TAG, "Failed to register display window listener!"); } + + mContext.getSystemService(DeviceStateManager.class) + .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener); } } diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index 0f97b9042ebe..b5846b555747 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -36,9 +36,9 @@ import com.android.server.compat.overrides.ChangeOverrides; import com.android.server.compat.overrides.OverrideValue; import com.android.server.compat.overrides.RawOverrideValue; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * Represents the state of a single compatibility change. @@ -82,8 +82,8 @@ public final class CompatChange extends CompatibilityChangeInfo { ChangeListener mListener = null; - private Map<String, Boolean> mEvaluatedOverrides; - private Map<String, PackageOverride> mRawOverrides; + private ConcurrentHashMap<String, Boolean> mEvaluatedOverrides; + private ConcurrentHashMap<String, PackageOverride> mRawOverrides; public CompatChange(long changeId) { this(changeId, null, -1, -1, false, false, null, false); @@ -114,11 +114,11 @@ public final class CompatChange extends CompatibilityChangeInfo { description, overridable); // Initialize override maps. - mEvaluatedOverrides = new HashMap<>(); - mRawOverrides = new HashMap<>(); + mEvaluatedOverrides = new ConcurrentHashMap<>(); + mRawOverrides = new ConcurrentHashMap<>(); } - void registerListener(ChangeListener listener) { + synchronized void registerListener(ChangeListener listener) { if (mListener != null) { throw new IllegalStateException( "Listener for change " + toString() + " already registered."); @@ -131,8 +131,6 @@ public final class CompatChange extends CompatibilityChangeInfo { * Force the enabled state of this change for a given package name. The change will only take * effect after that packages process is killed and restarted. * - * <p>Note, this method is not thread safe so callers must ensure thread safety. - * * @param pname Package name to enable the change for. * @param enabled Whether or not to enable the change. */ @@ -155,14 +153,12 @@ public final class CompatChange extends CompatibilityChangeInfo { * Tentatively set the state of this change for a given package name. * The override will only take effect after that package is installed, if applicable. * - * <p>Note, this method is not thread safe so callers must ensure thread safety. - * * @param packageName Package name to tentatively enable the change for. * @param override The package override to be set * @param allowedState Whether the override is allowed. * @param versionCode The version code of the package. */ - void addPackageOverride(String packageName, PackageOverride override, + synchronized void addPackageOverride(String packageName, PackageOverride override, OverrideAllowedState allowedState, @Nullable Long versionCode) { if (getLoggingOnly()) { throw new IllegalArgumentException( @@ -185,16 +181,17 @@ public final class CompatChange extends CompatibilityChangeInfo { * @return {@code true} if the recheck yielded a result that requires invalidating caches * (a deferred override was consolidated or a regular override was removed). */ - boolean recheckOverride(String packageName, OverrideAllowedState allowedState, + synchronized boolean recheckOverride(String packageName, OverrideAllowedState allowedState, @Nullable Long versionCode) { + if (packageName == null) { + return false; + } boolean allowed = (allowedState.state == OverrideAllowedState.ALLOWED); - // If the app is not installed or no longer has raw overrides, evaluate to false - if (versionCode == null || !hasRawOverride(packageName) || !allowed) { + if (versionCode == null || !mRawOverrides.containsKey(packageName) || !allowed) { removePackageOverrideInternal(packageName); return false; } - // Evaluate the override based on its version int overrideValue = mRawOverrides.get(packageName).evaluate(versionCode); switch (overrideValue) { @@ -211,10 +208,6 @@ public final class CompatChange extends CompatibilityChangeInfo { return true; } - boolean hasPackageOverride(String pname) { - return mRawOverrides.containsKey(pname); - } - /** * Remove any package override for the given package name, restoring the default behaviour. * @@ -224,9 +217,11 @@ public final class CompatChange extends CompatibilityChangeInfo { * @param allowedState Whether the override is allowed. * @param versionCode The version code of the package. */ - boolean removePackageOverride(String pname, OverrideAllowedState allowedState, + synchronized boolean removePackageOverride(String pname, OverrideAllowedState allowedState, @Nullable Long versionCode) { - if (mRawOverrides.remove(pname) != null) { + if (mRawOverrides.containsKey(pname)) { + allowedState.enforce(getId(), pname); + mRawOverrides.remove(pname); recheckOverride(pname, allowedState, versionCode); return true; } @@ -244,8 +239,11 @@ public final class CompatChange extends CompatibilityChangeInfo { if (app == null) { return defaultValue(); } - if (mEvaluatedOverrides.containsKey(app.packageName)) { - return mEvaluatedOverrides.get(app.packageName); + if (app.packageName != null) { + final Boolean enabled = mEvaluatedOverrides.get(app.packageName); + if (enabled != null) { + return enabled; + } } if (getDisabled()) { return false; @@ -269,9 +267,12 @@ public final class CompatChange extends CompatibilityChangeInfo { * @return {@code true} if the change should be enabled for the package. */ boolean willBeEnabled(String packageName) { - if (hasRawOverride(packageName)) { - int eval = mRawOverrides.get(packageName).evaluateForAllVersions(); - switch (eval) { + if (packageName == null) { + return defaultValue(); + } + final PackageOverride override = mRawOverrides.get(packageName); + if (override != null) { + switch (override.evaluateForAllVersions()) { case VALUE_ENABLED: return true; case VALUE_DISABLED: @@ -292,30 +293,12 @@ public final class CompatChange extends CompatibilityChangeInfo { return !getDisabled(); } - /** - * Checks whether a change has an override for a package. - * @param packageName name of the package - * @return true if there is such override - */ - private boolean hasOverride(String packageName) { - return mEvaluatedOverrides.containsKey(packageName); - } - - /** - * Checks whether a change has a deferred override for a package. - * @param packageName name of the package - * @return true if there is such a deferred override - */ - private boolean hasRawOverride(String packageName) { - return mRawOverrides.containsKey(packageName); - } - - void clearOverrides() { + synchronized void clearOverrides() { mRawOverrides.clear(); mEvaluatedOverrides.clear(); } - void loadOverrides(ChangeOverrides changeOverrides) { + synchronized void loadOverrides(ChangeOverrides changeOverrides) { // Load deferred overrides for backwards compatibility if (changeOverrides.getDeferred() != null) { for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) { @@ -348,7 +331,7 @@ public final class CompatChange extends CompatibilityChangeInfo { } } - ChangeOverrides saveOverrides() { + synchronized ChangeOverrides saveOverrides() { if (mRawOverrides.isEmpty()) { return null; } @@ -406,7 +389,7 @@ public final class CompatChange extends CompatibilityChangeInfo { return sb.append(")").toString(); } - private void notifyListener(String packageName) { + private synchronized void notifyListener(String packageName) { if (mListener != null) { mListener.onCompatChange(packageName); } diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 3faffe198ac9..f2408cfa4124 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -28,7 +28,6 @@ import android.content.pm.PackageManager; import android.os.Environment; import android.text.TextUtils; import android.util.LongArray; -import android.util.LongSparseArray; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -55,11 +54,12 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import javax.xml.datatype.DatatypeConfigurationException; @@ -76,9 +76,7 @@ final class CompatConfig { private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat"; private static final String OVERRIDES_FILE = "compat_framework_overrides.xml"; - private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock(); - @GuardedBy("mReadWriteLock") - private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>(); + private final ConcurrentHashMap<Long, CompatChange> mChanges = new ConcurrentHashMap<>(); private final OverrideValidatorImpl mOverrideValidator; private final AndroidBuildClassifier mAndroidBuildClassifier; @@ -113,21 +111,13 @@ final class CompatConfig { /** * Adds a change. * - * <p>This is intended to be used by code that reads change config from the filesystem. This - * should be done at system startup time. - * - * <p>Any change with the same ID will be overwritten. + * <p>This is intended to be used by unit tests only. * * @param change the change to add */ + @VisibleForTesting void addChange(CompatChange change) { - mReadWriteLock.writeLock().lock(); - try { - mChanges.put(change.getId(), change); - invalidateCache(); - } finally { - mReadWriteLock.writeLock().unlock(); - } + mChanges.put(change.getId(), change); } /** @@ -143,20 +133,14 @@ final class CompatConfig { */ long[] getDisabledChanges(ApplicationInfo app) { LongArray disabled = new LongArray(); - mReadWriteLock.readLock().lock(); - try { - for (int i = 0; i < mChanges.size(); ++i) { - CompatChange c = mChanges.valueAt(i); - if (!c.isEnabled(app, mAndroidBuildClassifier)) { - disabled.add(c.getId()); - } + for (CompatChange c : mChanges.values()) { + if (!c.isEnabled(app, mAndroidBuildClassifier)) { + disabled.add(c.getId()); } - } finally { - mReadWriteLock.readLock().unlock(); } - // Note: we don't need to explicitly sort the array, as the behaviour of LongSparseArray - // (mChanges) ensures it's already sorted. - return disabled.toArray(); + final long[] sortedChanges = disabled.toArray(); + Arrays.sort(sortedChanges); + return sortedChanges; } /** @@ -166,15 +150,10 @@ final class CompatConfig { * @return the change ID, or {@code -1} if no change with that name exists */ long lookupChangeId(String name) { - mReadWriteLock.readLock().lock(); - try { - for (int i = 0; i < mChanges.size(); ++i) { - if (TextUtils.equals(mChanges.valueAt(i).getName(), name)) { - return mChanges.keyAt(i); - } + for (CompatChange c : mChanges.values()) { + if (TextUtils.equals(c.getName(), name)) { + return c.getId(); } - } finally { - mReadWriteLock.readLock().unlock(); } return -1; } @@ -188,17 +167,12 @@ final class CompatConfig { * change ID is not known, as unknown changes are enabled by default. */ boolean isChangeEnabled(long changeId, ApplicationInfo app) { - mReadWriteLock.readLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - if (c == null) { - // we know nothing about this change: default behaviour is enabled. - return true; - } - return c.isEnabled(app, mAndroidBuildClassifier); - } finally { - mReadWriteLock.readLock().unlock(); + CompatChange c = mChanges.get(changeId); + if (c == null) { + // we know nothing about this change: default behaviour is enabled. + return true; } + return c.isEnabled(app, mAndroidBuildClassifier); } /** @@ -210,17 +184,12 @@ final class CompatConfig { * {@code true} if the change ID is not known, as unknown changes are enabled by default. */ boolean willChangeBeEnabled(long changeId, String packageName) { - mReadWriteLock.readLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - if (c == null) { - // we know nothing about this change: default behaviour is enabled. - return true; - } - return c.willBeEnabled(packageName); - } finally { - mReadWriteLock.readLock().unlock(); + CompatChange c = mChanges.get(changeId); + if (c == null) { + // we know nothing about this change: default behaviour is enabled. + return true; } + return c.willBeEnabled(packageName); } /** @@ -239,7 +208,7 @@ final class CompatConfig { * @return {@code true} if the change existed before adding the override * @throws IllegalStateException if overriding is not allowed */ - boolean addOverride(long changeId, String packageName, boolean enabled) { + synchronized boolean addOverride(long changeId, String packageName, boolean enabled) { boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, new PackageOverride.Builder().setEnabled(enabled).build()); saveOverrides(); @@ -250,13 +219,19 @@ final class CompatConfig { /** * Overrides the enabled state for a given change and app. * - * <p>Note, package overrides are not persistent and will be lost on system or runtime restart. * - * @param overrides list of overrides to default changes config. - * @param packageName app for which the overrides will be applied. + * @param overrides list of overrides to default changes config. + * @param packageName app for which the overrides will be applied. + * @param skipUnknownChangeIds whether to skip unknown change IDs in {@code overrides}. */ - void addOverrides(CompatibilityOverrideConfig overrides, String packageName) { + synchronized void addPackageOverrides(CompatibilityOverrideConfig overrides, + String packageName, boolean skipUnknownChangeIds) { for (Long changeId : overrides.overrides.keySet()) { + if (skipUnknownChangeIds && !isKnownChangeId(changeId)) { + Slog.w(TAG, "Trying to add overrides for unknown Change ID " + changeId + ". " + + "Skipping Change ID."); + continue; + } addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId)); } saveOverrides(); @@ -265,36 +240,24 @@ final class CompatConfig { private boolean addOverrideUnsafe(long changeId, String packageName, PackageOverride overrides) { - boolean alreadyKnown = true; + final AtomicBoolean alreadyKnown = new AtomicBoolean(true); OverrideAllowedState allowedState = mOverrideValidator.getOverrideAllowedState(changeId, packageName); allowedState.enforce(changeId, packageName); Long versionCode = getVersionCodeOrNull(packageName); - mReadWriteLock.writeLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - if (c == null) { - alreadyKnown = false; - c = new CompatChange(changeId); - addChange(c); - } - c.addPackageOverride(packageName, overrides, allowedState, versionCode); - invalidateCache(); - } finally { - mReadWriteLock.writeLock().unlock(); - } - return alreadyKnown; + + final CompatChange c = mChanges.computeIfAbsent(changeId, (key) -> { + alreadyKnown.set(false); + return new CompatChange(changeId); + }); + c.addPackageOverride(packageName, overrides, allowedState, versionCode); + invalidateCache(); + return alreadyKnown.get(); } /** Checks whether the change is known to the compat config. */ boolean isKnownChangeId(long changeId) { - mReadWriteLock.readLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - return c != null; - } finally { - mReadWriteLock.readLock().unlock(); - } + return mChanges.containsKey(changeId); } /** @@ -302,55 +265,35 @@ final class CompatConfig { * target SDK gated). */ int maxTargetSdkForChangeIdOptIn(long changeId) { - mReadWriteLock.readLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - if (c != null && c.getEnableSinceTargetSdk() != -1) { - return c.getEnableSinceTargetSdk() - 1; - } - return -1; - } finally { - mReadWriteLock.readLock().unlock(); + CompatChange c = mChanges.get(changeId); + if (c != null && c.getEnableSinceTargetSdk() != -1) { + return c.getEnableSinceTargetSdk() - 1; } + return -1; } /** * Returns whether the change is marked as logging only. */ boolean isLoggingOnly(long changeId) { - mReadWriteLock.readLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - return c != null && c.getLoggingOnly(); - } finally { - mReadWriteLock.readLock().unlock(); - } + CompatChange c = mChanges.get(changeId); + return c != null && c.getLoggingOnly(); } /** * Returns whether the change is marked as disabled. */ boolean isDisabled(long changeId) { - mReadWriteLock.readLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - return c != null && c.getDisabled(); - } finally { - mReadWriteLock.readLock().unlock(); - } + CompatChange c = mChanges.get(changeId); + return c != null && c.getDisabled(); } /** * Returns whether the change is overridable. */ boolean isOverridable(long changeId) { - mReadWriteLock.readLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - return c != null && c.getOverridable(); - } finally { - mReadWriteLock.readLock().unlock(); - } + CompatChange c = mChanges.get(changeId); + return c != null && c.getOverridable(); } /** @@ -363,10 +306,12 @@ final class CompatConfig { * @param packageName the app package name that was overridden * @return {@code true} if an override existed; */ - boolean removeOverride(long changeId, String packageName) { + synchronized boolean removeOverride(long changeId, String packageName) { boolean overrideExists = removeOverrideUnsafe(changeId, packageName); - saveOverrides(); - invalidateCache(); + if (overrideExists) { + saveOverrides(); + invalidateCache(); + } return overrideExists; } @@ -376,14 +321,9 @@ final class CompatConfig { */ private boolean removeOverrideUnsafe(long changeId, String packageName) { Long versionCode = getVersionCodeOrNull(packageName); - mReadWriteLock.writeLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - if (c != null) { - return removeOverrideUnsafe(c, packageName, versionCode); - } - } finally { - mReadWriteLock.writeLock().unlock(); + CompatChange c = mChanges.get(changeId); + if (c != null) { + return removeOverrideUnsafe(c, packageName, versionCode); } return false; } @@ -397,76 +337,71 @@ final class CompatConfig { long changeId = change.getId(); OverrideAllowedState allowedState = mOverrideValidator.getOverrideAllowedState(changeId, packageName); - if (change.hasPackageOverride(packageName)) { - allowedState.enforce(changeId, packageName); - change.removePackageOverride(packageName, allowedState, versionCode); - invalidateCache(); - return true; - } - return false; + return change.removePackageOverride(packageName, allowedState, versionCode); } /** * Removes all overrides previously added via {@link #addOverride(long, String, boolean)} or - * {@link #addOverrides(CompatibilityOverrideConfig, String)} for a certain package. + * {@link #addPackageOverrides(CompatibilityOverrideConfig, String, boolean)} for a certain + * package. * * <p>This restores the default behaviour for the given app. * * @param packageName the package for which the overrides should be purged */ - void removePackageOverrides(String packageName) { + synchronized void removePackageOverrides(String packageName) { Long versionCode = getVersionCodeOrNull(packageName); - mReadWriteLock.writeLock().lock(); - try { - for (int i = 0; i < mChanges.size(); ++i) { - CompatChange change = mChanges.valueAt(i); - removeOverrideUnsafe(change, packageName, versionCode); - } - } finally { - mReadWriteLock.writeLock().unlock(); + boolean shouldInvalidateCache = false; + for (CompatChange change : mChanges.values()) { + shouldInvalidateCache |= removeOverrideUnsafe(change, packageName, versionCode); + } + if (shouldInvalidateCache) { + saveOverrides(); + invalidateCache(); } - saveOverrides(); - invalidateCache(); } /** * Removes overrides whose change ID is specified in {@code overridesToRemove} that were * previously added via {@link #addOverride(long, String, boolean)} or - * {@link #addOverrides(CompatibilityOverrideConfig, String)} for a certain package. + * {@link #addPackageOverrides(CompatibilityOverrideConfig, String, boolean)} for a certain + * package. * * <p>This restores the default behaviour for the given change IDs and app. * * @param overridesToRemove list of change IDs for which to restore the default behaviour. * @param packageName the package for which the overrides should be purged */ - void removePackageOverrides(CompatibilityOverridesToRemoveConfig overridesToRemove, + synchronized void removePackageOverrides(CompatibilityOverridesToRemoveConfig overridesToRemove, String packageName) { + boolean shouldInvalidateCache = false; for (Long changeId : overridesToRemove.changeIds) { - removeOverrideUnsafe(changeId, packageName); + if (!isKnownChangeId(changeId)) { + Slog.w(TAG, "Trying to remove overrides for unknown Change ID " + changeId + ". " + + "Skipping Change ID."); + continue; + } + shouldInvalidateCache |= removeOverrideUnsafe(changeId, packageName); + } + if (shouldInvalidateCache) { + saveOverrides(); + invalidateCache(); } - saveOverrides(); - invalidateCache(); } private long[] getAllowedChangesSinceTargetSdkForPackage(String packageName, int targetSdkVersion) { LongArray allowed = new LongArray(); - mReadWriteLock.readLock().lock(); - try { - for (int i = 0; i < mChanges.size(); ++i) { - CompatChange change = mChanges.valueAt(i); - if (change.getEnableSinceTargetSdk() != targetSdkVersion) { - continue; - } - OverrideAllowedState allowedState = - mOverrideValidator.getOverrideAllowedState(change.getId(), - packageName); - if (allowedState.state == OverrideAllowedState.ALLOWED) { - allowed.add(change.getId()); - } + for (CompatChange change : mChanges.values()) { + if (change.getEnableSinceTargetSdk() != targetSdkVersion) { + continue; + } + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedState(change.getId(), + packageName); + if (allowedState.state == OverrideAllowedState.ALLOWED) { + allowed.add(change.getId()); } - } finally { - mReadWriteLock.readLock().unlock(); } return allowed.toArray(); } @@ -479,12 +414,15 @@ final class CompatConfig { */ int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) { long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion); + boolean shouldInvalidateCache = false; for (long changeId : changes) { - addOverrideUnsafe(changeId, packageName, + shouldInvalidateCache |= addOverrideUnsafe(changeId, packageName, new PackageOverride.Builder().setEnabled(true).build()); } - saveOverrides(); - invalidateCache(); + if (shouldInvalidateCache) { + saveOverrides(); + invalidateCache(); + } return changes.length; } @@ -496,30 +434,27 @@ final class CompatConfig { */ int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) { long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion); + boolean shouldInvalidateCache = false; for (long changeId : changes) { - addOverrideUnsafe(changeId, packageName, + shouldInvalidateCache |= addOverrideUnsafe(changeId, packageName, new PackageOverride.Builder().setEnabled(false).build()); } - saveOverrides(); - invalidateCache(); + if (shouldInvalidateCache) { + saveOverrides(); + invalidateCache(); + } return changes.length; } boolean registerListener(long changeId, CompatChange.ChangeListener listener) { - boolean alreadyKnown = true; - mReadWriteLock.writeLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - if (c == null) { - alreadyKnown = false; - c = new CompatChange(changeId); - addChange(c); - } - c.registerListener(listener); - } finally { - mReadWriteLock.writeLock().unlock(); - } - return alreadyKnown; + final AtomicBoolean alreadyKnown = new AtomicBoolean(true); + final CompatChange c = mChanges.computeIfAbsent(changeId, (key) -> { + alreadyKnown.set(false); + invalidateCache(); + return new CompatChange(changeId); + }); + c.registerListener(listener); + return alreadyKnown.get(); } boolean defaultChangeIdValue(long changeId) { @@ -537,12 +472,7 @@ final class CompatConfig { @VisibleForTesting void clearChanges() { - mReadWriteLock.writeLock().lock(); - try { - mChanges.clear(); - } finally { - mReadWriteLock.writeLock().unlock(); - } + mChanges.clear(); } /** @@ -551,18 +481,12 @@ final class CompatConfig { * @param pw {@link PrintWriter} instance to which the information will be dumped */ void dumpConfig(PrintWriter pw) { - mReadWriteLock.readLock().lock(); - try { - if (mChanges.size() == 0) { - pw.println("No compat overrides."); - return; - } - for (int i = 0; i < mChanges.size(); ++i) { - CompatChange c = mChanges.valueAt(i); - pw.println(c.toString()); - } - } finally { - mReadWriteLock.readLock().unlock(); + if (mChanges.size() == 0) { + pw.println("No compat overrides."); + return; + } + for (CompatChange c : mChanges.values()) { + pw.println(c.toString()); } } @@ -574,18 +498,12 @@ final class CompatConfig { CompatibilityChangeConfig getAppConfig(ApplicationInfo applicationInfo) { Set<Long> enabled = new HashSet<>(); Set<Long> disabled = new HashSet<>(); - mReadWriteLock.readLock().lock(); - try { - for (int i = 0; i < mChanges.size(); ++i) { - CompatChange c = mChanges.valueAt(i); - if (c.isEnabled(applicationInfo, mAndroidBuildClassifier)) { - enabled.add(c.getId()); - } else { - disabled.add(c.getId()); - } + for (CompatChange c : mChanges.values()) { + if (c.isEnabled(applicationInfo, mAndroidBuildClassifier)) { + enabled.add(c.getId()); + } else { + disabled.add(c.getId()); } - } finally { - mReadWriteLock.readLock().unlock(); } return new CompatibilityChangeConfig(new ChangeConfig(enabled, disabled)); } @@ -596,17 +514,12 @@ final class CompatConfig { * @return an array of {@link CompatibilityChangeInfo} with the current changes */ CompatibilityChangeInfo[] dumpChanges() { - mReadWriteLock.readLock().lock(); - try { - CompatibilityChangeInfo[] changeInfos = new CompatibilityChangeInfo[mChanges.size()]; - for (int i = 0; i < mChanges.size(); ++i) { - CompatChange change = mChanges.valueAt(i); - changeInfos[i] = new CompatibilityChangeInfo(change); - } - return changeInfos; - } finally { - mReadWriteLock.readLock().unlock(); + CompatibilityChangeInfo[] changeInfos = new CompatibilityChangeInfo[mChanges.size()]; + int i = 0; + for (CompatChange change : mChanges.values()) { + changeInfos[i++] = new CompatibilityChangeInfo(change); } + return changeInfos; } void initConfigFromLib(File libraryDir) { @@ -626,10 +539,12 @@ final class CompatConfig { Config config = com.android.server.compat.config.XmlParser.read(in); for (Change change : config.getCompatChange()) { Slog.d(TAG, "Adding: " + change.toString()); - addChange(new CompatChange(change)); + mChanges.put(change.getId(), new CompatChange(change)); } } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { Slog.e(TAG, "Encountered an error while reading/parsing compat config file", e); + } finally { + invalidateCache(); } } @@ -641,15 +556,12 @@ final class CompatConfig { @VisibleForTesting void initOverrides(File dynamicOverridesFile, File staticOverridesFile) { // Clear overrides from all changes before loading. - mReadWriteLock.writeLock().lock(); - try { - for (int i = 0; i < mChanges.size(); ++i) { - mChanges.valueAt(i).clearOverrides(); - } - } finally { - mReadWriteLock.writeLock().unlock(); + + for (CompatChange c : mChanges.values()) { + c.clearOverrides(); } + loadOverrides(staticOverridesFile); mOverridesFile = dynamicOverridesFile; @@ -698,18 +610,12 @@ final class CompatConfig { } synchronized (mOverridesFile) { Overrides overrides = new Overrides(); - mReadWriteLock.readLock().lock(); - try { - List<ChangeOverrides> changeOverridesList = overrides.getChangeOverrides(); - for (int idx = 0; idx < mChanges.size(); ++idx) { - CompatChange c = mChanges.valueAt(idx); - ChangeOverrides changeOverrides = c.saveOverrides(); - if (changeOverrides != null) { - changeOverridesList.add(changeOverrides); - } + List<ChangeOverrides> changeOverridesList = overrides.getChangeOverrides(); + for (CompatChange c : mChanges.values()) { + ChangeOverrides changeOverrides = c.saveOverrides(); + if (changeOverrides != null) { + changeOverridesList.add(changeOverrides); } - } finally { - mReadWriteLock.readLock().unlock(); } // Create the file if it doesn't already exist try { @@ -741,20 +647,11 @@ final class CompatConfig { void recheckOverrides(String packageName) { Long versionCode = getVersionCodeOrNull(packageName); boolean shouldInvalidateCache = false; - mReadWriteLock.readLock().lock(); - try { - for (int idx = 0; idx < mChanges.size(); ++idx) { - CompatChange c = mChanges.valueAt(idx); - if (!c.hasPackageOverride(packageName)) { - continue; - } - OverrideAllowedState allowedState = - mOverrideValidator.getOverrideAllowedStateForRecheck(c.getId(), - packageName); - shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, versionCode); - } - } finally { - mReadWriteLock.readLock().unlock(); + for (CompatChange c : mChanges.values()) { + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedStateForRecheck(c.getId(), + packageName); + shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, versionCode); } if (shouldInvalidateCache) { invalidateCache(); diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index b32d1d749680..6ea89d445402 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -205,7 +205,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { overridesMap.put(change, new PackageOverride.Builder().setEnabled(false) .build()); } - mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName); + mCompatConfig.addPackageOverrides(new CompatibilityOverrideConfig(overridesMap), + packageName, /* skipUnknownChangeIds */ false); killPackage(packageName); } @@ -220,7 +221,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { overridesMap.put(change, new PackageOverride.Builder().setEnabled(false) .build()); } - mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName); + mCompatConfig.addPackageOverrides(new CompatibilityOverrideConfig(overridesMap), + packageName, /* skipUnknownChangeIds */ false); } @Override @@ -229,7 +231,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { // TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods. checkCompatChangeOverrideOverridablePermission(); checkAllCompatOverridesAreOverridable(overrides.overrides.keySet()); - mCompatConfig.addOverrides(overrides, packageName); + mCompatConfig.addPackageOverrides(overrides, packageName, /* skipUnknownChangeIds= */ true); } @Override @@ -435,7 +437,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { private void checkAllCompatOverridesAreOverridable(Collection<Long> changeIds) { for (Long changeId : changeIds) { - if (!mCompatConfig.isOverridable(changeId)) { + if (isKnownChangeId(changeId) && !mCompatConfig.isOverridable(changeId)) { throw new SecurityException("Only change ids marked as Overridable can be " + "overridden."); } diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java new file mode 100644 index 000000000000..11dc1dbd25cc --- /dev/null +++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2021 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.compat.overrides; + +import static android.content.pm.PackageManager.MATCH_ANY_USER; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; + +import android.app.compat.PackageOverride; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.KeyValueListParser; +import android.util.Slog; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * A utility class for parsing App Compat Overrides flags. + * + * @hide + */ +final class AppCompatOverridesParser { + /** + * Flag for specifying all compat change IDs owned by a namespace. See {@link + * #parseOwnedChangeIds} for information on how this flag is parsed. + */ + static final String FLAG_OWNED_CHANGE_IDS = "owned_change_ids"; + + /** + * Flag for immediately removing overrides for certain packages and change IDs (from the compat + * platform), as well as stopping to apply them, in case of an emergency. See {@link + * #parseRemoveOverrides} for information on how this flag is parsed. + */ + static final String FLAG_REMOVE_OVERRIDES = "remove_overrides"; + + private static final String TAG = "AppCompatOverridesParser"; + + private static final String WILDCARD_SYMBOL = "*"; + + private static final Pattern BOOLEAN_PATTERN = + Pattern.compile("true|false", Pattern.CASE_INSENSITIVE); + + private static final String WILDCARD_NO_OWNED_CHANGE_IDS_WARNING = + "Wildcard can't be used in '" + FLAG_REMOVE_OVERRIDES + "' flag with an empty " + + FLAG_OWNED_CHANGE_IDS + "' flag"; + + private final PackageManager mPackageManager; + + AppCompatOverridesParser(PackageManager packageManager) { + mPackageManager = packageManager; + } + + /** + * Parses the given {@code configStr} and returns a map from package name to a set of change + * IDs to remove for that package. + * + * <p>The given {@code configStr} is expected to either be: + * + * <ul> + * <li>'*' (wildcard), to indicate that all owned overrides, specified in {@code + * ownedChangeIds}, for all installed packages should be removed. + * <li>A comma separated key value list, where the key is a package name and the value is + * either: + * <ul> + * <li>'*' (wildcard), to indicate that all owned overrides, specified in {@code + * ownedChangeIds} for that package should be removed. + * <li>A colon separated list of change IDs to remove for that package. + * </ul> + * </ul> + * + * <p>If the given {@code configStr} doesn't match the expected format, an empty map will be + * returned. If a specific change ID isn't a valid long, it will be ignored. + */ + Map<String, Set<Long>> parseRemoveOverrides(String configStr, Set<Long> ownedChangeIds) { + if (configStr.isEmpty()) { + return emptyMap(); + } + + Map<String, Set<Long>> result = new ArrayMap<>(); + if (configStr.equals(WILDCARD_SYMBOL)) { + if (ownedChangeIds.isEmpty()) { + Slog.w(TAG, WILDCARD_NO_OWNED_CHANGE_IDS_WARNING); + return emptyMap(); + } + List<ApplicationInfo> installedApps = mPackageManager.getInstalledApplications( + MATCH_ANY_USER); + for (ApplicationInfo appInfo : installedApps) { + result.put(appInfo.packageName, ownedChangeIds); + } + return result; + } + + KeyValueListParser parser = new KeyValueListParser(','); + try { + parser.setString(configStr); + } catch (IllegalArgumentException e) { + Slog.w( + TAG, + "Invalid format in '" + FLAG_REMOVE_OVERRIDES + "' flag: " + configStr, e); + return emptyMap(); + } + for (int i = 0; i < parser.size(); i++) { + String packageName = parser.keyAt(i); + String changeIdsStr = parser.getString(packageName, /* def= */ ""); + if (changeIdsStr.equals(WILDCARD_SYMBOL)) { + if (ownedChangeIds.isEmpty()) { + Slog.w(TAG, WILDCARD_NO_OWNED_CHANGE_IDS_WARNING); + continue; + } + result.put(packageName, ownedChangeIds); + } else { + for (String changeIdStr : changeIdsStr.split(":")) { + try { + long changeId = Long.parseLong(changeIdStr); + result.computeIfAbsent(packageName, k -> new ArraySet<>()).add(changeId); + } catch (NumberFormatException e) { + Slog.w( + TAG, + "Invalid change ID in '" + FLAG_REMOVE_OVERRIDES + "' flag: " + + changeIdStr, e); + } + } + } + } + + return result; + } + + + /** + * Parses the given {@code configStr}, that is expected to be a comma separated list of change + * IDs, into a set. + * + * <p>If any of the change IDs isn't a valid long, it will be ignored. + */ + static Set<Long> parseOwnedChangeIds(String configStr) { + if (configStr.isEmpty()) { + return emptySet(); + } + + Set<Long> result = new ArraySet<>(); + for (String changeIdStr : configStr.split(",")) { + try { + result.add(Long.parseLong(changeIdStr)); + } catch (NumberFormatException e) { + Slog.w(TAG, + "Invalid change ID in '" + FLAG_OWNED_CHANGE_IDS + "' flag: " + changeIdStr, + e); + } + } + return result; + } + + /** + * Parses the given {@code configStr}, that is expected to be a comma separated list of changes + * overrides, and returns a map from change ID to {@link PackageOverride} instances to add. + * + * <p>Each change override is in the following format: + * '<change-id>:<min-version-code?>:<max-version-code?>:<enabled>'. + * + * <p>If there are multiple overrides that should be added with the same change ID, the one + * that best fits the given {@code versionCode} is added. + * + * <p>Any overrides whose change ID is in {@code changeIdsToSkip} are ignored. + * + * <p>If a change override entry in {@code configStr} is invalid, it will be ignored. + */ + static Map<Long, PackageOverride> parsePackageOverrides(String configStr, long versionCode, + Set<Long> changeIdsToSkip) { + if (configStr.isEmpty()) { + return emptyMap(); + } + PackageOverrideComparator comparator = new PackageOverrideComparator(versionCode); + Map<Long, PackageOverride> overridesToAdd = new ArrayMap<>(); + for (String overrideEntryString : configStr.split(",")) { + List<String> changeIdAndVersions = Arrays.asList(overrideEntryString.split(":", 4)); + if (changeIdAndVersions.size() != 4) { + Slog.w(TAG, "Invalid change override entry: " + overrideEntryString); + continue; + } + long changeId; + try { + changeId = Long.parseLong(changeIdAndVersions.get(0)); + } catch (NumberFormatException e) { + Slog.w(TAG, "Invalid change ID in override entry: " + overrideEntryString, e); + continue; + } + + if (changeIdsToSkip.contains(changeId)) { + continue; + } + + String minVersionCodeStr = changeIdAndVersions.get(1); + String maxVersionCodeStr = changeIdAndVersions.get(2); + + String enabledStr = changeIdAndVersions.get(3); + if (!BOOLEAN_PATTERN.matcher(enabledStr).matches()) { + Slog.w(TAG, "Invalid enabled string in override entry: " + overrideEntryString); + continue; + } + boolean enabled = Boolean.parseBoolean(enabledStr); + PackageOverride.Builder overrideBuilder = new PackageOverride.Builder().setEnabled( + enabled); + try { + if (!minVersionCodeStr.isEmpty()) { + overrideBuilder.setMinVersionCode(Long.parseLong(minVersionCodeStr)); + } + if (!maxVersionCodeStr.isEmpty()) { + overrideBuilder.setMaxVersionCode(Long.parseLong(maxVersionCodeStr)); + } + } catch (NumberFormatException e) { + Slog.w(TAG, + "Invalid min/max version code in override entry: " + overrideEntryString, + e); + continue; + } + + try { + PackageOverride override = overrideBuilder.build(); + if (!overridesToAdd.containsKey(changeId) + || comparator.compare(override, overridesToAdd.get(changeId)) < 0) { + overridesToAdd.put(changeId, override); + } + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed to build PackageOverride", e); + } + } + + return overridesToAdd; + } + + /** + * A {@link Comparator} that compares @link PackageOverride} instances with respect to a + * specified {@code versionCode} as follows: + * + * <ul> + * <li>Prefer the {@link PackageOverride} whose version range contains {@code versionCode}. + * <li>Otherwise, prefer the {@link PackageOverride} whose version range is closest to {@code + * versionCode} from below. + * <li>Otherwise, prefer the {@link PackageOverride} whose version range is closest to {@code + * versionCode} from above. + * </ul> + */ + private static final class PackageOverrideComparator implements Comparator<PackageOverride> { + private final long mVersionCode; + + PackageOverrideComparator(long versionCode) { + this.mVersionCode = versionCode; + } + + @Override + public int compare(PackageOverride o1, PackageOverride o2) { + // Prefer overrides whose version range contains versionCode. + boolean isVersionInRange1 = isVersionInRange(o1, mVersionCode); + boolean isVersionInRange2 = isVersionInRange(o2, mVersionCode); + if (isVersionInRange1 != isVersionInRange2) { + return isVersionInRange1 ? -1 : 1; + } + + // Otherwise, prefer overrides whose version range is before versionCode. + boolean isVersionAfterRange1 = isVersionAfterRange(o1, mVersionCode); + boolean isVersionAfterRange2 = isVersionAfterRange(o2, mVersionCode); + if (isVersionAfterRange1 != isVersionAfterRange2) { + return isVersionAfterRange1 ? -1 : 1; + } + + // If both overrides' version ranges are either before or after versionCode, prefer + // those whose version range is closer to versionCode. + return Long.compare( + getVersionProximity(o1, mVersionCode), getVersionProximity(o2, mVersionCode)); + } + + /** + * Returns true if the version range in the given {@code override} contains {@code + * versionCode}. + */ + private static boolean isVersionInRange(PackageOverride override, long versionCode) { + return override.getMinVersionCode() <= versionCode + && versionCode <= override.getMaxVersionCode(); + } + + /** + * Returns true if the given {@code versionCode} is strictly after the version range in the + * given {@code override}. + */ + private static boolean isVersionAfterRange(PackageOverride override, long versionCode) { + return override.getMaxVersionCode() < versionCode; + } + + /** + * Returns true if the given {@code versionCode} is strictly before the version range in the + * given {@code override}. + */ + private static boolean isVersionBeforeRange(PackageOverride override, long versionCode) { + return override.getMinVersionCode() > versionCode; + } + + /** + * In case the given {@code versionCode} is strictly before or after the version range in + * the given {@code override}, returns the distance from it, otherwise returns zero. + */ + private static long getVersionProximity(PackageOverride override, long versionCode) { + if (isVersionAfterRange(override, versionCode)) { + return versionCode - override.getMaxVersionCode(); + } + if (isVersionBeforeRange(override, versionCode)) { + return override.getMinVersionCode() - versionCode; + } + + // Version is in range. Note that when two overrides have a zero version proximity + // they will be ordered arbitrarily. + return 0; + } + } +} diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java new file mode 100644 index 000000000000..6aed4b023297 --- /dev/null +++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2021 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.compat.overrides; + +import static android.content.Intent.ACTION_PACKAGE_ADDED; +import static android.content.Intent.ACTION_PACKAGE_CHANGED; +import static android.content.Intent.ACTION_PACKAGE_REMOVED; +import static android.content.pm.PackageManager.MATCH_ANY_USER; +import static android.provider.DeviceConfig.NAMESPACE_APP_COMPAT_OVERRIDES; + +import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_OWNED_CHANGE_IDS; +import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_REMOVE_OVERRIDES; + +import static java.util.Collections.emptySet; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.compat.PackageOverride; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.Properties; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.compat.CompatibilityOverrideConfig; +import com.android.internal.compat.CompatibilityOverridesToRemoveConfig; +import com.android.internal.compat.IPlatformCompat; +import com.android.server.SystemService; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Service for applying per-app compat overrides delivered via Device Config. + * + * <p>The service listens both on changes to supported Device Config namespaces and on package + * added/changed/removed events, and applies overrides accordingly. + * + * @hide + */ +public final class AppCompatOverridesService { + private static final String TAG = "AppCompatOverridesService"; + + private static final List<String> SUPPORTED_NAMESPACES = Arrays.asList( + NAMESPACE_APP_COMPAT_OVERRIDES); + + private final Context mContext; + private final PackageManager mPackageManager; + private final IPlatformCompat mPlatformCompat; + private final List<String> mSupportedNamespaces; + private final AppCompatOverridesParser mOverridesParser; + private final PackageReceiver mPackageReceiver; + private final List<DeviceConfigListener> mDeviceConfigListeners; + + private AppCompatOverridesService(Context context) { + this(context, IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)), SUPPORTED_NAMESPACES); + } + + @VisibleForTesting + AppCompatOverridesService(Context context, IPlatformCompat platformCompat, + List<String> supportedNamespaces) { + mContext = context; + mPackageManager = mContext.getPackageManager(); + mPlatformCompat = platformCompat; + mSupportedNamespaces = supportedNamespaces; + mOverridesParser = new AppCompatOverridesParser(mPackageManager); + mPackageReceiver = new PackageReceiver(mContext); + mDeviceConfigListeners = new ArrayList<>(); + for (String namespace : mSupportedNamespaces) { + mDeviceConfigListeners.add(new DeviceConfigListener(mContext, namespace)); + } + } + + @Override + public void finalize() { + unregisterDeviceConfigListeners(); + unregisterPackageReceiver(); + } + + @VisibleForTesting + void registerDeviceConfigListeners() { + for (DeviceConfigListener listener : mDeviceConfigListeners) { + listener.register(); + } + } + + private void unregisterDeviceConfigListeners() { + for (DeviceConfigListener listener : mDeviceConfigListeners) { + listener.unregister(); + } + } + + @VisibleForTesting + void registerPackageReceiver() { + mPackageReceiver.register(); + } + + private void unregisterPackageReceiver() { + mPackageReceiver.unregister(); + } + + /** + * Same as {@link #applyOverrides(Properties, Set, Map)} except all properties of the given + * {@code namespace} are fetched via {@link DeviceConfig#getProperties}. + */ + private void applyAllOverrides(String namespace, Set<Long> ownedChangeIds, + Map<String, Set<Long>> packageToChangeIdsToSkip) { + applyOverrides(DeviceConfig.getProperties(namespace), ownedChangeIds, + packageToChangeIdsToSkip); + } + + /** + * Iterates all package override flags in the given {@code properties}, and for each flag whose + * package is installed on the device, parses its value and adds the overrides in it with + * respect to the package's current installed version. + * + * <p>In addition, for each package, removes any override that wasn't just added, whose change + * ID is in {@code ownedChangeIds} but not in the respective set in {@code + * packageToChangeIdsToSkip}. + */ + private void applyOverrides(Properties properties, Set<Long> ownedChangeIds, + Map<String, Set<Long>> packageToChangeIdsToSkip) { + Set<String> packageNames = new ArraySet<>(properties.getKeyset()); + packageNames.remove(FLAG_OWNED_CHANGE_IDS); + packageNames.remove(FLAG_REMOVE_OVERRIDES); + for (String packageName : packageNames) { + Long versionCode = getVersionCodeOrNull(packageName); + if (versionCode == null) { + // Package isn't installed yet. + continue; + } + + applyPackageOverrides(properties.getString(packageName, /* defaultValue= */ ""), + packageName, versionCode, ownedChangeIds, + packageToChangeIdsToSkip.getOrDefault(packageName, emptySet()), + /* removeOtherOwnedOverrides= */ true); + } + } + + /** + * Adds all overrides in all supported namespaces for the given {@code packageName}. + */ + private void addAllPackageOverrides(String packageName) { + Long versionCode = getVersionCodeOrNull(packageName); + if (versionCode == null) { + return; + } + + for (String namespace : mSupportedNamespaces) { + // We apply overrides for each namespace separately so that if there is a failure for + // one namespace, the other namespaces won't be affected. + Set<Long> ownedChangeIds = getOwnedChangeIds(namespace); + applyPackageOverrides( + DeviceConfig.getString(namespace, packageName, /* defaultValue= */ ""), + packageName, versionCode, ownedChangeIds, + getOverridesToRemove(namespace, ownedChangeIds).getOrDefault(packageName, + emptySet()), /* removeOtherOwnedOverrides */ false); + } + } + + /** + * Calls {@link AppCompatOverridesParser#parsePackageOverrides} on the given arguments and adds + * the resulting overrides via {@link IPlatformCompat#putOverridesOnReleaseBuilds}. + * + * <p>In addition, if {@code removeOtherOwnedOverrides} is true, removes any override that + * wasn't just added, whose change ID is in {@code ownedChangeIds} but not in {@code + * changeIdsToSkip}, via {@link IPlatformCompat#removeOverridesOnReleaseBuilds}. + */ + private void applyPackageOverrides(String configStr, String packageName, long versionCode, + Set<Long> ownedChangeIds, Set<Long> changeIdsToSkip, + boolean removeOtherOwnedOverrides) { + Map<Long, PackageOverride> overridesToAdd = AppCompatOverridesParser.parsePackageOverrides( + configStr, versionCode, changeIdsToSkip); + putPackageOverrides(packageName, overridesToAdd); + + if (!removeOtherOwnedOverrides) { + return; + } + Set<Long> overridesToRemove = new ArraySet<>(); + for (Long changeId : ownedChangeIds) { + if (!overridesToAdd.containsKey(changeId) && !changeIdsToSkip.contains(changeId)) { + overridesToRemove.add(changeId); + } + } + removePackageOverrides(packageName, overridesToRemove); + } + + /** + * Removes all owned overrides in all supported namespaces for the given {@code packageName}. + * + * <p>If a certain namespace doesn't have a package override flag for the given {@code + * packageName}, that namespace is skipped.</p> + */ + private void removeAllPackageOverrides(String packageName) { + for (String namespace : mSupportedNamespaces) { + if (DeviceConfig.getString(namespace, packageName, /* defaultValue= */ "").isEmpty()) { + // No overrides for this package in this namespace. + continue; + } + // We remove overrides for each namespace separately so that if there is a failure for + // one namespace, the other namespaces won't be affected. + removePackageOverrides(packageName, getOwnedChangeIds(namespace)); + } + } + + /** + * Calls {@link IPlatformCompat#removeOverridesOnReleaseBuilds} on each package name and + * respective change IDs in {@code overridesToRemove}. + */ + private void removeOverrides(Map<String, Set<Long>> overridesToRemove) { + for (Map.Entry<String, Set<Long>> packageNameAndOverrides : overridesToRemove.entrySet()) { + removePackageOverrides(packageNameAndOverrides.getKey(), + packageNameAndOverrides.getValue()); + } + } + + /** + * Fetches the value of {@link AppCompatOverridesParser#FLAG_REMOVE_OVERRIDES} for the given + * {@code namespace} and parses it into a map from package name to a set of change IDs to + * remove for that package. + */ + private Map<String, Set<Long>> getOverridesToRemove(String namespace, + Set<Long> ownedChangeIds) { + return mOverridesParser.parseRemoveOverrides( + DeviceConfig.getString(namespace, FLAG_REMOVE_OVERRIDES, /* defaultValue= */ ""), + ownedChangeIds); + } + + /** + * Fetches the value of {@link AppCompatOverridesParser#FLAG_OWNED_CHANGE_IDS} for the given + * {@code namespace} and parses it into a set of change IDs. + */ + private static Set<Long> getOwnedChangeIds(String namespace) { + return AppCompatOverridesParser.parseOwnedChangeIds( + DeviceConfig.getString(namespace, FLAG_OWNED_CHANGE_IDS, /* defaultValue= */ "")); + } + + private void putPackageOverrides(String packageName, + Map<Long, PackageOverride> overridesToAdd) { + if (overridesToAdd.isEmpty()) { + return; + } + CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overridesToAdd); + try { + mPlatformCompat.putOverridesOnReleaseBuilds(config, packageName); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call IPlatformCompat#putOverridesOnReleaseBuilds", e); + } + } + + private void removePackageOverrides(String packageName, Set<Long> overridesToRemove) { + if (overridesToRemove.isEmpty()) { + return; + } + CompatibilityOverridesToRemoveConfig config = new CompatibilityOverridesToRemoveConfig( + overridesToRemove); + try { + mPlatformCompat.removeOverridesOnReleaseBuilds(config, packageName); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call IPlatformCompat#removeOverridesOnReleaseBuilds", e); + } + } + + private boolean isInstalledForAnyUser(String packageName) { + return getVersionCodeOrNull(packageName) != null; + } + + @Nullable + private Long getVersionCodeOrNull(String packageName) { + try { + ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(packageName, + MATCH_ANY_USER); + return applicationInfo.longVersionCode; + } catch (PackageManager.NameNotFoundException e) { + // Package isn't installed for any user. + return null; + } + } + + /** + * SystemService lifecycle for AppCompatOverridesService. + * + * @hide + */ + public static final class Lifecycle extends SystemService { + private AppCompatOverridesService mService; + + public Lifecycle(Context context) { + super(context); + } + + @Override + public void onStart() { + mService = new AppCompatOverridesService(getContext()); + mService.registerDeviceConfigListeners(); + mService.registerPackageReceiver(); + } + } + + /** + * A {@link DeviceConfig.OnPropertiesChangedListener} that listens on changes to a given + * namespace and adds/removes overrides according to the changed flags. + */ + private final class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener { + private final Context mContext; + private final String mNamespace; + + private DeviceConfigListener(Context context, String namespace) { + mContext = context; + mNamespace = namespace; + } + + private void register() { + DeviceConfig.addOnPropertiesChangedListener(mNamespace, mContext.getMainExecutor(), + this); + } + + private void unregister() { + DeviceConfig.removeOnPropertiesChangedListener(this); + } + + @Override + public void onPropertiesChanged(Properties properties) { + boolean removeOverridesFlagChanged = properties.getKeyset().contains( + FLAG_REMOVE_OVERRIDES); + boolean ownedChangedIdsFlagChanged = properties.getKeyset().contains( + FLAG_OWNED_CHANGE_IDS); + + Set<Long> ownedChangeIds = getOwnedChangeIds(mNamespace); + Map<String, Set<Long>> overridesToRemove = getOverridesToRemove(mNamespace, + ownedChangeIds); + if (removeOverridesFlagChanged || ownedChangedIdsFlagChanged) { + // In both cases it's possible that overrides that weren't removed before should + // now be removed. + removeOverrides(overridesToRemove); + } + + if (removeOverridesFlagChanged) { + // We need to re-apply all overrides in the namespace since the remove overrides + // flag might have blocked some of them from being applied before. + applyAllOverrides(mNamespace, ownedChangeIds, overridesToRemove); + } else { + applyOverrides(properties, ownedChangeIds, overridesToRemove); + } + } + } + + /** + * A {@link BroadcastReceiver} that listens on package added/changed/removed events and + * adds/removes overrides according to the corresponding Device Config flags. + */ + private final class PackageReceiver extends BroadcastReceiver { + private final Context mContext; + private final IntentFilter mIntentFilter; + + private PackageReceiver(Context context) { + mContext = context; + mIntentFilter = new IntentFilter(); + mIntentFilter.addAction(ACTION_PACKAGE_ADDED); + mIntentFilter.addAction(ACTION_PACKAGE_CHANGED); + mIntentFilter.addAction(ACTION_PACKAGE_REMOVED); + mIntentFilter.addDataScheme("package"); + } + + private void register() { + mContext.registerReceiverForAllUsers(this, mIntentFilter, /* broadcastPermission= */ + null, /* scheduler= */ null); + } + + private void unregister() { + mContext.unregisterReceiver(this); + } + + @Override + public void onReceive(@NonNull final Context context, @NonNull final Intent intent) { + Uri data = intent.getData(); + if (data == null) { + Slog.w(TAG, "Failed to get package name in package receiver"); + return; + } + String packageName = data.getSchemeSpecificPart(); + String action = intent.getAction(); + if (action == null) { + Slog.w(TAG, "Failed to get action in package receiver"); + return; + } + switch (action) { + case ACTION_PACKAGE_ADDED: + case ACTION_PACKAGE_CHANGED: + addAllPackageOverrides(packageName); + break; + case ACTION_PACKAGE_REMOVED: + if (!isInstalledForAnyUser(packageName)) { + removeAllPackageOverrides(packageName); + } + break; + default: + Slog.w(TAG, "Unsupported action in package receiver: " + action); + break; + } + } + }; +} diff --git a/services/core/java/com/android/server/compat/overrides/OWNERS b/services/core/java/com/android/server/compat/overrides/OWNERS new file mode 100644 index 000000000000..b80f3402c19d --- /dev/null +++ b/services/core/java/com/android/server/compat/overrides/OWNERS @@ -0,0 +1,2 @@ +tomnatan@google.com +mariiasand@google.com diff --git a/services/core/java/com/android/server/compat/overrides/TEST_MAPPING b/services/core/java/com/android/server/compat/overrides/TEST_MAPPING new file mode 100644 index 000000000000..4b8f08ec9164 --- /dev/null +++ b/services/core/java/com/android/server/compat/overrides/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "presubmit": [ + { + "name": "FrameworksMockingServicesTests", + "options": [ + { + "include-filter": "com.android.server.compat.overrides" + } + ] + } + ] +} diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java index 091e6c4adf4d..a56a8ea993f0 100644 --- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java +++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java @@ -227,7 +227,7 @@ public class MultipathPolicyTracker { subscriberId = tele.getSubscriberId(); mNetworkTemplate = new NetworkTemplate( NetworkTemplate.MATCH_MOBILE, subscriberId, new String[] { subscriberId }, - null, NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL, + null, NetworkStats.METERED_YES, NetworkStats.ROAMING_ALL, NetworkStats.DEFAULT_NETWORK_NO, NETWORK_TYPE_ALL, OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT); mUsageCallback = new UsageCallback() { diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 1acbde91b353..3762ccaae13b 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -17,6 +17,8 @@ package com.android.server.connectivity; import static android.Manifest.permission.BIND_VPN_SERVICE; +import static android.Manifest.permission.CONTROL_VPN; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNREACHABLE; @@ -891,6 +893,7 @@ public class Vpn { * - oldPackage null, newPackage non-null: ConfirmDialog calling prepareVpn(). * - oldPackage null, newPackage=LEGACY_VPN: Used internally to disconnect * and revoke any current app VPN and re-prepare legacy vpn. + * - oldPackage null, newPackage null: always returns true for backward compatibility. * * TODO: Rename the variables - or split this method into two - and end this confusion. * TODO: b/29032008 Migrate code from prepare(oldPackage=non-null, newPackage=LEGACY_VPN) @@ -904,6 +907,18 @@ public class Vpn { */ public synchronized boolean prepare( String oldPackage, String newPackage, @VpnManager.VpnType int vpnType) { + // Except for Settings and VpnDialogs, the caller should be matched one of oldPackage or + // newPackage. Otherwise, non VPN owner might get the VPN always-on status of the VPN owner. + // See b/191382886. + if (mContext.checkCallingOrSelfPermission(CONTROL_VPN) != PERMISSION_GRANTED) { + if (oldPackage != null) { + verifyCallingUidAndPackage(oldPackage); + } + if (newPackage != null) { + verifyCallingUidAndPackage(newPackage); + } + } + if (oldPackage != null) { // Stop an existing always-on VPN from being dethroned by other apps. if (mAlwaysOn && !isCurrentPreparedPackage(oldPackage)) { @@ -1803,14 +1818,13 @@ public class Vpn { } private void enforceControlPermission() { - mContext.enforceCallingPermission(Manifest.permission.CONTROL_VPN, "Unauthorized Caller"); + mContext.enforceCallingPermission(CONTROL_VPN, "Unauthorized Caller"); } private void enforceControlPermissionOrInternalCaller() { // Require the caller to be either an application with CONTROL_VPN permission or a process // in the system server. - mContext.enforceCallingOrSelfPermission(Manifest.permission.CONTROL_VPN, - "Unauthorized Caller"); + mContext.enforceCallingOrSelfPermission(CONTROL_VPN, "Unauthorized Caller"); } private void enforceSettingsPermission() { @@ -3115,8 +3129,9 @@ public class Vpn { } private void verifyCallingUidAndPackage(String packageName) { - if (getAppUid(packageName, mUserId) != Binder.getCallingUid()) { - throw new SecurityException("Mismatched package and UID"); + final int callingUid = Binder.getCallingUid(); + if (getAppUid(packageName, mUserId) != callingUid) { + throw new SecurityException(packageName + " does not belong to uid " + callingUid); } } diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java index e693bcc93f8f..7fe24ff1f069 100644 --- a/services/core/java/com/android/server/devicestate/DeviceState.java +++ b/services/core/java/com/android/server/devicestate/DeviceState.java @@ -19,11 +19,14 @@ package com.android.server.devicestate; import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -39,6 +42,19 @@ import java.util.Objects; * @see DeviceStateManagerService */ public final class DeviceState { + /** + * Flag that indicates sticky requests should be cancelled when this device state becomes the + * base device state. + */ + public static final int FLAG_CANCEL_STICKY_REQUESTS = 1 << 0; + + /** @hide */ + @IntDef(prefix = {"FLAG_"}, flag = true, value = { + FLAG_CANCEL_STICKY_REQUESTS, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeviceStateFlags {} + /** Unique identifier for the device state. */ @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) private final int mIdentifier; @@ -47,14 +63,19 @@ public final class DeviceState { @NonNull private final String mName; + @DeviceStateFlags + private final int mFlags; + public DeviceState( @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier, - @NonNull String name) { + @NonNull String name, + @DeviceStateFlags int flags) { Preconditions.checkArgumentInRange(identifier, MINIMUM_DEVICE_STATE, MAXIMUM_DEVICE_STATE, "identifier"); mIdentifier = identifier; mName = name; + mFlags = flags; } /** Returns the unique identifier for the device state. */ @@ -69,6 +90,11 @@ public final class DeviceState { return mName; } + @DeviceStateFlags + public int getFlags() { + return mFlags; + } + @Override public String toString() { return "DeviceState{" + "identifier=" + mIdentifier + ", name='" + mName + '\'' + '}'; @@ -80,11 +106,12 @@ public final class DeviceState { if (o == null || getClass() != o.getClass()) return false; DeviceState that = (DeviceState) o; return mIdentifier == that.mIdentifier - && Objects.equals(mName, that.mName); + && Objects.equals(mName, that.mName) + && mFlags == that.mFlags; } @Override public int hashCode() { - return Objects.hash(mIdentifier, mName); + return Objects.hash(mIdentifier, mName, mFlags); } } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index a8b0994402e8..806a5dd65a13 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -19,8 +19,12 @@ package com.android.server.devicestate; import static android.Manifest.permission.CONTROL_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; -import static android.hardware.devicestate.DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES; +import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE; +import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED; +import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED; + +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,12 +34,11 @@ import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.IDeviceStateManager; import android.hardware.devicestate.IDeviceStateManagerCallback; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; -import android.util.ArrayMap; -import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; @@ -43,14 +46,21 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; +import com.android.server.DisplayThread; +import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.policy.DeviceStatePolicyImpl; +import com.android.server.wm.ActivityTaskManagerInternal; +import com.android.server.wm.WindowProcessController; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Optional; +import java.util.WeakHashMap; /** * A system service that manages the state of a device with user-configurable hardware like a @@ -81,10 +91,19 @@ public final class DeviceStateManagerService extends SystemService { private static final boolean DEBUG = false; private final Object mLock = new Object(); + // Handler on the {@link DisplayThread} used to dispatch calls to the policy and to registered + // callbacks though its handler (mHandler). Provides a guarantee of callback order when + // leveraging mHandler and also enables posting messages with the service lock held. + private final Handler mHandler; @NonNull private final DeviceStatePolicy mDeviceStatePolicy; @NonNull private final BinderService mBinderService; + @NonNull + private final OverrideRequestController mOverrideRequestController; + @VisibleForTesting + @NonNull + public ActivityTaskManagerInternal mActivityTaskManagerInternal; // All supported device states keyed by identifier. @GuardedBy("mLock") @@ -109,17 +128,16 @@ public final class DeviceStateManagerService extends SystemService { @NonNull private Optional<DeviceState> mBaseState = Optional.empty(); + // The current active override request. When set the device state specified here will take + // precedence over mBaseState. + @GuardedBy("mLock") + @NonNull + private Optional<OverrideRequest> mActiveOverride = Optional.empty(); + // List of processes registered to receive notifications about changes to device state and // request status indexed by process id. @GuardedBy("mLock") private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>(); - // List of override requests with the highest precedence request at the end. - @GuardedBy("mLock") - private final ArrayList<OverrideRequestRecord> mRequestRecords = new ArrayList<>(); - // Set of override requests that are pending a call to notifyStatusIfNeeded() to be notified - // of a change in status. - @GuardedBy("mLock") - private final ArraySet<OverrideRequestRecord> mRequestsPendingStatusChange = new ArraySet<>(); public DeviceStateManagerService(@NonNull Context context) { this(context, new DeviceStatePolicyImpl(context)); @@ -128,9 +146,16 @@ public final class DeviceStateManagerService extends SystemService { @VisibleForTesting DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy) { super(context); + // We use the DisplayThread because this service indirectly drives + // display (on/off) and window (position) events through its callbacks. + DisplayThread displayThread = DisplayThread.get(); + mHandler = new Handler(displayThread.getLooper()); + mOverrideRequestController = new OverrideRequestController( + this::onOverrideRequestStatusChangedLocked); mDeviceStatePolicy = policy; mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener()); mBinderService = new BinderService(); + mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); } @Override @@ -138,6 +163,11 @@ public final class DeviceStateManagerService extends SystemService { publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService); } + @VisibleForTesting + Handler getHandler() { + return mHandler; + } + /** * Returns the current state the system is in. Note that the system may be in the process of * configuring a different state. @@ -191,12 +221,10 @@ public final class DeviceStateManagerService extends SystemService { @NonNull Optional<DeviceState> getOverrideState() { synchronized (mLock) { - if (mRequestRecords.isEmpty()) { - return Optional.empty(); + if (mActiveOverride.isPresent()) { + return getStateLocked(mActiveOverride.get().getRequestedState()); } - - OverrideRequestRecord topRequest = mRequestRecords.get(mRequestRecords.size() - 1); - return Optional.of(topRequest.mRequestedState); + return Optional.empty(); } } @@ -247,43 +275,41 @@ public final class DeviceStateManagerService extends SystemService { } private void updateSupportedStates(DeviceState[] supportedDeviceStates) { - boolean updatedPendingState; - boolean hasBaseState; synchronized (mLock) { final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked(); + // Whether or not at least one device state has the flag FLAG_CANCEL_STICKY_REQUESTS + // set. If set to true, the OverrideRequestController will be configured to allow sticky + // requests. + boolean hasTerminalDeviceState = false; mDeviceStates.clear(); for (int i = 0; i < supportedDeviceStates.length; i++) { DeviceState state = supportedDeviceStates[i]; + if ((state.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) { + hasTerminalDeviceState = true; + } mDeviceStates.put(state.getIdentifier(), state); } + mOverrideRequestController.setStickyRequestsAllowed(hasTerminalDeviceState); + final int[] newStateIdentifiers = getSupportedStateIdentifiersLocked(); if (Arrays.equals(oldStateIdentifiers, newStateIdentifiers)) { return; } - final int requestSize = mRequestRecords.size(); - for (int i = 0; i < requestSize; i++) { - OverrideRequestRecord request = mRequestRecords.get(i); - if (!isSupportedStateLocked(request.mRequestedState.getIdentifier())) { - request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED); - } - } + mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers); + updatePendingStateLocked(); - updatedPendingState = updatePendingStateLocked(); - hasBaseState = mBaseState.isPresent(); - } + if (!mPendingState.isPresent()) { + // If the change in the supported states didn't result in a change of the pending + // state commitPendingState() will never be called and the callbacks will never be + // notified of the change. + notifyDeviceStateInfoChangedAsync(); + } - if (hasBaseState && !updatedPendingState) { - // If the change in the supported states didn't result in a change of the pending state - // commitPendingState() will never be called and the callbacks will never be notified - // of the change. - notifyDeviceStateInfoChanged(); + mHandler.post(this::notifyPolicyIfNeeded); } - - notifyRequestsOfStatusChangeIfNeeded(); - notifyPolicyIfNeeded(); } /** @@ -311,7 +337,6 @@ public final class DeviceStateManagerService extends SystemService { * @see #isSupportedStateLocked(int) */ private void setBaseState(int identifier) { - boolean updatedPendingState; synchronized (mLock) { final Optional<DeviceState> baseStateOptional = getStateLocked(identifier); if (!baseStateOptional.isPresent()) { @@ -325,26 +350,21 @@ public final class DeviceStateManagerService extends SystemService { } mBaseState = Optional.of(baseState); - final int requestSize = mRequestRecords.size(); - for (int i = 0; i < requestSize; i++) { - OverrideRequestRecord request = mRequestRecords.get(i); - if ((request.mFlags & FLAG_CANCEL_WHEN_BASE_CHANGES) > 0) { - request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED); - } + if ((baseState.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) { + mOverrideRequestController.cancelStickyRequests(); } + mOverrideRequestController.handleBaseStateChanged(); + updatePendingStateLocked(); - updatedPendingState = updatePendingStateLocked(); - } + if (!mPendingState.isPresent()) { + // If the change in base state didn't result in a change of the pending state + // commitPendingState() will never be called and the callbacks will never be + // notified of the change. + notifyDeviceStateInfoChangedAsync(); + } - if (!updatedPendingState) { - // If the change in base state didn't result in a change of the pending state - // commitPendingState() will never be called and the callbacks will never be notified - // of the change. - notifyDeviceStateInfoChanged(); + mHandler.post(this::notifyPolicyIfNeeded); } - - notifyRequestsOfStatusChangeIfNeeded(); - notifyPolicyIfNeeded(); } /** @@ -362,8 +382,8 @@ public final class DeviceStateManagerService extends SystemService { } final DeviceState stateToConfigure; - if (!mRequestRecords.isEmpty()) { - stateToConfigure = mRequestRecords.get(mRequestRecords.size() - 1).mRequestedState; + if (mActiveOverride.isPresent()) { + stateToConfigure = getStateLocked(mActiveOverride.get().getRequestedState()).get(); } else if (mBaseState.isPresent() && isSupportedStateLocked(mBaseState.get().getIdentifier())) { // Base state could have recently become unsupported after a change in supported states. @@ -429,108 +449,106 @@ public final class DeviceStateManagerService extends SystemService { * </p> */ private void commitPendingState() { - // Update the current state. synchronized (mLock) { final DeviceState newState = mPendingState.get(); if (DEBUG) { Slog.d(TAG, "Committing state: " + newState); } - if (!mRequestRecords.isEmpty()) { - final OverrideRequestRecord topRequest = - mRequestRecords.get(mRequestRecords.size() - 1); - if (topRequest.mRequestedState.getIdentifier() == newState.getIdentifier()) { - // The top request could have come in while the service was awaiting callback - // from the policy. In that case we only set it to active if it matches the - // current committed state, otherwise it will be set to active when its - // requested state is committed. - topRequest.setStatusLocked(OverrideRequestRecord.STATUS_ACTIVE); - } - } - FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_STATE_CHANGED, newState.getIdentifier(), !mCommittedState.isPresent()); mCommittedState = Optional.of(newState); mPendingState = Optional.empty(); updatePendingStateLocked(); - } - - // Notify callbacks of a change. - notifyDeviceStateInfoChanged(); - // Notify the top request that it's active. - notifyRequestsOfStatusChangeIfNeeded(); - - // Try to configure the next state if needed. - notifyPolicyIfNeeded(); - } + // Notify callbacks of a change. + notifyDeviceStateInfoChangedAsync(); + + // The top request could have come in while the service was awaiting callback + // from the policy. In that case we only set it to active if it matches the + // current committed state, otherwise it will be set to active when its + // requested state is committed. + OverrideRequest activeRequest = mActiveOverride.orElse(null); + if (activeRequest != null + && activeRequest.getRequestedState() == newState.getIdentifier()) { + ProcessRecord processRecord = mProcessRecords.get(activeRequest.getPid()); + if (processRecord != null) { + processRecord.notifyRequestActiveAsync(activeRequest.getToken()); + } + } - private void notifyDeviceStateInfoChanged() { - if (Thread.holdsLock(mLock)) { - throw new IllegalStateException( - "Attempting to notify callbacks with service lock held."); + // Try to configure the next state if needed. + mHandler.post(this::notifyPolicyIfNeeded); } + } - // Grab the lock and copy the process records and the current info. - ArrayList<ProcessRecord> registeredProcesses; - DeviceStateInfo info; + private void notifyDeviceStateInfoChangedAsync() { synchronized (mLock) { if (mProcessRecords.size() == 0) { return; } - registeredProcesses = new ArrayList<>(); + ArrayList<ProcessRecord> registeredProcesses = new ArrayList<>(); for (int i = 0; i < mProcessRecords.size(); i++) { registeredProcesses.add(mProcessRecords.valueAt(i)); } - info = getDeviceStateInfoLocked(); - } + DeviceStateInfo info = getDeviceStateInfoLocked(); - // After releasing the lock, send the notifications out. - for (int i = 0; i < registeredProcesses.size(); i++) { - registeredProcesses.get(i).notifyDeviceStateInfoAsync(info); + for (int i = 0; i < registeredProcesses.size(); i++) { + registeredProcesses.get(i).notifyDeviceStateInfoAsync(info); + } } } - /** - * Notifies all dirty requests (requests that have a change in status, but have not yet been - * notified) that their status has changed. - */ - private void notifyRequestsOfStatusChangeIfNeeded() { - if (Thread.holdsLock(mLock)) { - throw new IllegalStateException( - "Attempting to notify requests with service lock held."); + private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request, + @OverrideRequestController.RequestStatus int status) { + if (status == STATUS_ACTIVE) { + mActiveOverride = Optional.of(request); + } else if (status == STATUS_SUSPENDED || status == STATUS_CANCELED) { + if (mActiveOverride.isPresent() && mActiveOverride.get() == request) { + mActiveOverride = Optional.empty(); + } + } else { + throw new IllegalArgumentException("Unknown request status: " + status); } - ArraySet<OverrideRequestRecord> dirtyRequests; - synchronized (mLock) { - if (mRequestsPendingStatusChange.isEmpty()) { - return; - } + boolean updatedPendingState = updatePendingStateLocked(); - dirtyRequests = new ArraySet<>(mRequestsPendingStatusChange); - mRequestsPendingStatusChange.clear(); + ProcessRecord processRecord = mProcessRecords.get(request.getPid()); + if (processRecord == null) { + // If the process is no longer registered with the service, for example if it has died, + // there is no need to notify it of a change in request status. + mHandler.post(this::notifyPolicyIfNeeded); + return; } - // After releasing the lock, send the notifications out. - for (int i = 0; i < dirtyRequests.size(); i++) { - dirtyRequests.valueAt(i).notifyStatusIfNeeded(); + if (status == STATUS_ACTIVE) { + if (!updatedPendingState && !mPendingState.isPresent()) { + // If the pending state was not updated and there is not currently a pending state + // then this newly active request will never be notified of a change in state. + // Schedule the notification now. + processRecord.notifyRequestActiveAsync(request.getToken()); + } + } else if (status == STATUS_SUSPENDED) { + processRecord.notifyRequestSuspendedAsync(request.getToken()); + } else { + processRecord.notifyRequestCanceledAsync(request.getToken()); } + + mHandler.post(this::notifyPolicyIfNeeded); } private void registerProcess(int pid, IDeviceStateManagerCallback callback) { - DeviceStateInfo currentInfo; - ProcessRecord record; - // Grab the lock to register the callback and get the current state. synchronized (mLock) { if (mProcessRecords.contains(pid)) { throw new SecurityException("The calling process has already registered an" + " IDeviceStateManagerCallback."); } - record = new ProcessRecord(callback, pid); + ProcessRecord record = new ProcessRecord(callback, pid, this::handleProcessDied, + mHandler); try { callback.asBinder().linkToDeath(record, 0); } catch (RemoteException ex) { @@ -538,34 +556,21 @@ public final class DeviceStateManagerService extends SystemService { } mProcessRecords.put(pid, record); - currentInfo = mCommittedState.isPresent() ? getDeviceStateInfoLocked() : null; - } - - if (currentInfo != null) { - // If there is not a committed state we'll wait to notify the process of the initial - // value. - record.notifyDeviceStateInfoAsync(currentInfo); + DeviceStateInfo currentInfo = mCommittedState.isPresent() + ? getDeviceStateInfoLocked() : null; + if (currentInfo != null) { + // If there is not a committed state we'll wait to notify the process of the initial + // value. + record.notifyDeviceStateInfoAsync(currentInfo); + } } } private void handleProcessDied(ProcessRecord processRecord) { synchronized (mLock) { - // Cancel all requests from this process. - final int requestCount = processRecord.mRequestRecords.size(); - for (int i = 0; i < requestCount; i++) { - final OverrideRequestRecord request = processRecord.mRequestRecords.valueAt(i); - // Cancel the request but don't mark it as dirty since there's no need to send - // notifications if the process has died. - request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED, - false /* markDirty */); - } - mProcessRecords.remove(processRecord.mPid); - - updatePendingStateLocked(); + mOverrideRequestController.handleProcessDied(processRecord.mPid); } - - notifyPolicyIfNeeded(); } private void requestStateInternal(int state, int flags, int callingPid, @@ -577,7 +582,7 @@ public final class DeviceStateManagerService extends SystemService { + " has no registered callback."); } - if (processRecord.mRequestRecords.get(token) != null) { + if (mOverrideRequestController.hasRequest(token)) { throw new IllegalStateException("Request has already been made for the supplied" + " token: " + token); } @@ -588,27 +593,9 @@ public final class DeviceStateManagerService extends SystemService { + " is not supported."); } - OverrideRequestRecord topRecord = mRequestRecords.isEmpty() - ? null : mRequestRecords.get(mRequestRecords.size() - 1); - if (topRecord != null) { - topRecord.setStatusLocked(OverrideRequestRecord.STATUS_SUSPENDED); - } - - final OverrideRequestRecord request = - new OverrideRequestRecord(processRecord, token, deviceState.get(), flags); - mRequestRecords.add(request); - processRecord.mRequestRecords.put(request.mToken, request); - - final boolean updatedPendingState = updatePendingStateLocked(); - if (!updatedPendingState && !mPendingState.isPresent()) { - // We don't set the status of the new request to ACTIVE if the request updated the - // pending state as it will be set in commitPendingState(). - request.setStatusLocked(OverrideRequestRecord.STATUS_ACTIVE, true /* markDirty */); - } + OverrideRequest request = new OverrideRequest(token, callingPid, state, flags); + mOverrideRequestController.addRequest(request); } - - notifyRequestsOfStatusChangeIfNeeded(); - notifyPolicyIfNeeded(); } private void cancelRequestInternal(int callingPid, @NonNull IBinder token) { @@ -619,18 +606,8 @@ public final class DeviceStateManagerService extends SystemService { + " has no registered callback."); } - OverrideRequestRecord request = processRecord.mRequestRecords.get(token); - if (request == null) { - throw new IllegalStateException("No known request for the given token"); - } - - request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED); - - updatePendingStateLocked(); + mOverrideRequestController.cancelRequest(token); } - - notifyRequestsOfStatusChangeIfNeeded(); - notifyPolicyIfNeeded(); } private void dumpInternal(PrintWriter pw) { @@ -650,16 +627,7 @@ public final class DeviceStateManagerService extends SystemService { pw.println(" " + i + ": mPid=" + processRecord.mPid); } - final int requestCount = mRequestRecords.size(); - pw.println(); - pw.println("Override requests: size=" + requestCount); - for (int i = 0; i < requestCount; i++) { - OverrideRequestRecord requestRecord = mRequestRecords.get(i); - pw.println(" " + i + ": mPid=" + requestRecord.mProcessRecord.mPid - + ", mRequestedState=" + requestRecord.mRequestedState - + ", mFlags=" + requestRecord.mFlags - + ", mStatus=" + requestRecord.statusToString(requestRecord.mStatus)); - } + mOverrideRequestController.dumpInternal(pw); } } @@ -683,142 +651,107 @@ public final class DeviceStateManagerService extends SystemService { } } - private final class ProcessRecord implements IBinder.DeathRecipient { + private static final class ProcessRecord implements IBinder.DeathRecipient { + public interface DeathListener { + void onProcessDied(ProcessRecord record); + } + + private static final int STATUS_ACTIVE = 0; + + private static final int STATUS_SUSPENDED = 1; + + private static final int STATUS_CANCELED = 2; + + @IntDef(prefix = {"STATUS_"}, value = { + STATUS_ACTIVE, + STATUS_SUSPENDED, + STATUS_CANCELED + }) + @Retention(RetentionPolicy.SOURCE) + private @interface RequestStatus {} + private final IDeviceStateManagerCallback mCallback; private final int mPid; + private final DeathListener mDeathListener; + private final Handler mHandler; - private final ArrayMap<IBinder, OverrideRequestRecord> mRequestRecords = new ArrayMap<>(); + private final WeakHashMap<IBinder, Integer> mLastNotifiedStatus = new WeakHashMap<>(); - ProcessRecord(IDeviceStateManagerCallback callback, int pid) { + ProcessRecord(IDeviceStateManagerCallback callback, int pid, DeathListener deathListener, + Handler handler) { mCallback = callback; mPid = pid; + mDeathListener = deathListener; + mHandler = handler; } @Override public void binderDied() { - handleProcessDied(this); + mDeathListener.onProcessDied(this); } public void notifyDeviceStateInfoAsync(@NonNull DeviceStateInfo info) { - try { - mCallback.onDeviceStateInfoChanged(info); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify process " + mPid + " that device state changed.", - ex); - } + mHandler.post(() -> { + try { + mCallback.onDeviceStateInfoChanged(info); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + mPid + " that device state changed.", + ex); + } + }); } - public void notifyRequestActiveAsync(OverrideRequestRecord request) { - try { - mCallback.onRequestActive(request.mToken); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.", - ex); + public void notifyRequestActiveAsync(IBinder token) { + @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token); + if (lastStatus != null + && (lastStatus == STATUS_ACTIVE || lastStatus == STATUS_CANCELED)) { + return; } - } - public void notifyRequestSuspendedAsync(OverrideRequestRecord request) { - try { - mCallback.onRequestSuspended(request.mToken); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.", - ex); - } + mLastNotifiedStatus.put(token, STATUS_ACTIVE); + mHandler.post(() -> { + try { + mCallback.onRequestActive(token); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.", + ex); + } + }); } - public void notifyRequestCanceledAsync(OverrideRequestRecord request) { - try { - mCallback.onRequestCanceled(request.mToken); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.", - ex); + public void notifyRequestSuspendedAsync(IBinder token) { + @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token); + if (lastStatus != null + && (lastStatus == STATUS_SUSPENDED || lastStatus == STATUS_CANCELED)) { + return; } - } - } - - /** A record describing a request to override the state of the device. */ - private final class OverrideRequestRecord { - public static final int STATUS_UNKNOWN = 0; - public static final int STATUS_ACTIVE = 1; - public static final int STATUS_SUSPENDED = 2; - public static final int STATUS_CANCELED = 3; - - @Nullable - public String statusToString(int status) { - switch (status) { - case STATUS_ACTIVE: - return "ACTIVE"; - case STATUS_SUSPENDED: - return "SUSPENDED"; - case STATUS_CANCELED: - return "CANCELED"; - case STATUS_UNKNOWN: - return "UNKNOWN"; - default: - return null; - } - } - - private final ProcessRecord mProcessRecord; - @NonNull - private final IBinder mToken; - @NonNull - private final DeviceState mRequestedState; - private final int mFlags; - - private int mStatus = STATUS_UNKNOWN; - private int mLastNotifiedStatus = STATUS_UNKNOWN; - - OverrideRequestRecord(@NonNull ProcessRecord processRecord, @NonNull IBinder token, - @NonNull DeviceState requestedState, int flags) { - mProcessRecord = processRecord; - mToken = token; - mRequestedState = requestedState; - mFlags = flags; - } - - public void setStatusLocked(int status) { - setStatusLocked(status, true /* markDirty */); - } - - public void setStatusLocked(int status, boolean markDirty) { - if (mStatus != status) { - if (mStatus == STATUS_CANCELED) { - throw new IllegalStateException( - "Can not alter the status of a request after set to CANCELED."); - } - - mStatus = status; - - if (mStatus == STATUS_CANCELED) { - mRequestRecords.remove(this); - mProcessRecord.mRequestRecords.remove(mToken); - } - if (markDirty) { - mRequestsPendingStatusChange.add(this); + mLastNotifiedStatus.put(token, STATUS_SUSPENDED); + mHandler.post(() -> { + try { + mCallback.onRequestSuspended(token); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.", + ex); } - } + }); } - public void notifyStatusIfNeeded() { - int stateToReport; - synchronized (mLock) { - if (mLastNotifiedStatus == mStatus) { - return; - } - - stateToReport = mStatus; - mLastNotifiedStatus = mStatus; + public void notifyRequestCanceledAsync(IBinder token) { + @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token); + if (lastStatus != null && lastStatus == STATUS_CANCELED) { + return; } - if (stateToReport == STATUS_ACTIVE) { - mProcessRecord.notifyRequestActiveAsync(this); - } else if (stateToReport == STATUS_SUSPENDED) { - mProcessRecord.notifyRequestSuspendedAsync(this); - } else if (stateToReport == STATUS_CANCELED) { - mProcessRecord.notifyRequestCanceledAsync(this); - } + mLastNotifiedStatus.put(token, STATUS_CANCELED); + mHandler.post(() -> { + try { + mCallback.onRequestCanceled(token); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.", + ex); + } + }); } } @@ -848,14 +781,21 @@ public final class DeviceStateManagerService extends SystemService { @Override // Binder call public void requestState(IBinder token, int state, int flags) { - getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, - "Permission required to request device state."); + final int callingPid = Binder.getCallingPid(); + // Allow top processes to request a device state change + // If the calling process ID is not the top app, then we check if this process + // holds a permission to CONTROL_DEVICE_STATE + final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp(); + if (topApp.getPid() != callingPid) { + getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, + "Permission required to request device state, " + + "or the call must come from the top focused app."); + } if (token == null) { throw new IllegalArgumentException("Request token must not be null."); } - final int callingPid = Binder.getCallingPid(); final long callingIdentity = Binder.clearCallingIdentity(); try { requestStateInternal(state, flags, callingPid, token); @@ -866,14 +806,21 @@ public final class DeviceStateManagerService extends SystemService { @Override // Binder call public void cancelRequest(IBinder token) { - getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, - "Permission required to clear requested device state."); + final int callingPid = Binder.getCallingPid(); + // Allow top processes to cancel a device state change + // If the calling process ID is not the top app, then we check if this process + // holds a permission to CONTROL_DEVICE_STATE + final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp(); + if (topApp.getPid() != callingPid) { + getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, + "Permission required to cancel device state, " + + "or the call must come from the top focused app."); + } if (token == null) { throw new IllegalArgumentException("Request token must not be null."); } - final int callingPid = Binder.getCallingPid(); final long callingIdentity = Binder.clearCallingIdentity(); try { cancelRequestInternal(callingPid, token); diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java index 56b68b73cb57..eed68f8b1300 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java @@ -27,7 +27,9 @@ import android.os.Binder; import android.os.ShellCommand; import java.io.PrintWriter; +import java.util.Arrays; import java.util.Optional; +import java.util.stream.Collectors; /** * ShellCommands for {@link DeviceStateManagerService}. @@ -56,14 +58,18 @@ public class DeviceStateManagerShellCommand extends ShellCommand { switch (cmd) { case "state": return runState(pw); + case "print-state": + return runPrintState(pw); case "print-states": return runPrintStates(pw); + case "print-states-simple": + return runPrintStatesSimple(pw); default: return handleDefaultCommands(cmd); } } - private void printState(PrintWriter pw) { + private void printAllStates(PrintWriter pw) { Optional<DeviceState> committedState = mService.getCommittedState(); Optional<DeviceState> baseState = mService.getBaseState(); Optional<DeviceState> overrideState = mService.getOverrideState(); @@ -79,7 +85,8 @@ public class DeviceStateManagerShellCommand extends ShellCommand { private int runState(PrintWriter pw) { final String nextArg = getNextArg(); if (nextArg == null) { - printState(pw); + printAllStates(pw); + return 0; } final Context context = mService.getContext(); @@ -123,6 +130,16 @@ public class DeviceStateManagerShellCommand extends ShellCommand { return 0; } + private int runPrintState(PrintWriter pw) { + Optional<DeviceState> deviceState = mService.getCommittedState(); + if (deviceState.isPresent()) { + pw.println(deviceState.get().getIdentifier()); + return 0; + } + getErrPrintWriter().println("Error: device state not available."); + return 1; + } + private int runPrintStates(PrintWriter pw) { DeviceState[] states = mService.getSupportedStates(); pw.print("Supported states: [\n"); @@ -133,6 +150,14 @@ public class DeviceStateManagerShellCommand extends ShellCommand { return 0; } + private int runPrintStatesSimple(PrintWriter pw) { + pw.print(Arrays.stream(mService.getSupportedStates()) + .map(DeviceState::getIdentifier) + .map(Object::toString) + .collect(Collectors.joining(","))); + return 0; + } + @Override public void onHelp() { PrintWriter pw = getOutPrintWriter(); @@ -141,8 +166,12 @@ public class DeviceStateManagerShellCommand extends ShellCommand { pw.println(" Print this help text."); pw.println(" state [reset|OVERRIDE_DEVICE_STATE]"); pw.println(" Return or override device state."); + pw.println(" print-state"); + pw.println(" Return the current device state."); pw.println(" print-states"); pw.println(" Return list of currently supported device states."); + pw.println(" print-states-simple"); + pw.println(" Return the currently supported device states in comma separated format."); } private static String toString(@NonNull Optional<DeviceState> state) { diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java new file mode 100644 index 000000000000..35a4c844c710 --- /dev/null +++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 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.devicestate; + +import android.hardware.devicestate.DeviceStateRequest; +import android.os.IBinder; + +/** + * A request to override the state managed by {@link DeviceStateManagerService}. + * + * @see OverrideRequestController + */ +final class OverrideRequest { + private final IBinder mToken; + private final int mPid; + private final int mRequestedState; + @DeviceStateRequest.RequestFlags + private final int mFlags; + + OverrideRequest(IBinder token, int pid, int requestedState, + @DeviceStateRequest.RequestFlags int flags) { + mToken = token; + mPid = pid; + mRequestedState = requestedState; + mFlags = flags; + } + + IBinder getToken() { + return mToken; + } + + int getPid() { + return mPid; + } + + int getRequestedState() { + return mRequestedState; + } + + @DeviceStateRequest.RequestFlags + int getFlags() { + return mFlags; + } +} diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java new file mode 100644 index 000000000000..05c9eb2c5bbe --- /dev/null +++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2021 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.devicestate; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.devicestate.DeviceStateRequest; +import android.os.IBinder; + +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +/** + * Manages the lifecycle of override requests. + * <p> + * New requests are added with {@link #addRequest(OverrideRequest)} and are kept active until + * either: + * <ul> + * <li>A new request is added with {@link #addRequest(OverrideRequest)}, in which case the + * request will become suspended.</li> + * <li>The request is cancelled with {@link #cancelRequest(IBinder)} or as a side effect + * of other methods calls, such as {@link #handleProcessDied(int)}.</li> + * </ul> + */ +final class OverrideRequestController { + static final int STATUS_UNKNOWN = 0; + /** + * The request is the top-most request. + */ + static final int STATUS_ACTIVE = 1; + /** + * The request is still present but is being superseded by another request. + */ + static final int STATUS_SUSPENDED = 2; + /** + * The request is not longer valid. + */ + static final int STATUS_CANCELED = 3; + + @IntDef(prefix = {"STATUS_"}, value = { + STATUS_UNKNOWN, + STATUS_ACTIVE, + STATUS_SUSPENDED, + STATUS_CANCELED + }) + @Retention(RetentionPolicy.SOURCE) + @interface RequestStatus {} + + static String statusToString(@RequestStatus int status) { + switch (status) { + case STATUS_ACTIVE: + return "ACTIVE"; + case STATUS_SUSPENDED: + return "SUSPENDED"; + case STATUS_CANCELED: + return "CANCELED"; + case STATUS_UNKNOWN: + return "UNKNOWN"; + } + throw new IllegalArgumentException("Unknown status: " + status); + } + + private final StatusChangeListener mListener; + private final List<OverrideRequest> mTmpRequestsToCancel = new ArrayList<>(); + + // List of override requests with the most recent override request at the end. + private final ArrayList<OverrideRequest> mRequests = new ArrayList<>(); + + private boolean mStickyRequestsAllowed; + // List of override requests that have outlived their process and will only be cancelled through + // a call to cancelStickyRequests(). + private final ArrayList<OverrideRequest> mStickyRequests = new ArrayList<>(); + + OverrideRequestController(@NonNull StatusChangeListener listener) { + mListener = listener; + } + + /** + * Sets sticky requests as either allowed or disallowed. When sticky requests are allowed a call + * to {@link #handleProcessDied(int)} will not result in the request being cancelled + * immediately. Instead, the request will be marked sticky and must be cancelled with a call + * to {@link #cancelStickyRequests()}. + */ + void setStickyRequestsAllowed(boolean stickyRequestsAllowed) { + mStickyRequestsAllowed = stickyRequestsAllowed; + if (!mStickyRequestsAllowed) { + cancelStickyRequests(); + } + } + + /** + * Adds a request to the top of the stack and notifies the listener of all changes to request + * status as a result of this operation. + */ + void addRequest(@NonNull OverrideRequest request) { + mRequests.add(request); + mListener.onStatusChanged(request, STATUS_ACTIVE); + + if (mRequests.size() > 1) { + OverrideRequest prevRequest = mRequests.get(mRequests.size() - 2); + mListener.onStatusChanged(prevRequest, STATUS_SUSPENDED); + } + } + + /** + * Cancels the request with the specified {@code token} and notifies the listener of all changes + * to request status as a result of this operation. + */ + void cancelRequest(@NonNull IBinder token) { + int index = getRequestIndex(token); + if (index == -1) { + return; + } + + OverrideRequest request = mRequests.remove(index); + if (index == mRequests.size() && mRequests.size() > 0) { + // We removed the current active request so we need to set the new active request + // before cancelling this request. + OverrideRequest newTop = getLast(mRequests); + mListener.onStatusChanged(newTop, STATUS_ACTIVE); + } + mListener.onStatusChanged(request, STATUS_CANCELED); + } + + /** + * Cancels all requests that are currently marked sticky and notifies the listener of all + * changes to request status as a result of this operation. + * + * @see #setStickyRequestsAllowed(boolean) + */ + void cancelStickyRequests() { + mTmpRequestsToCancel.clear(); + mTmpRequestsToCancel.addAll(mStickyRequests); + cancelRequestsLocked(mTmpRequestsToCancel); + } + + /** + * Returns {@code true} if this controller is current managing a request with the specified + * {@code token}, {@code false} otherwise. + */ + boolean hasRequest(@NonNull IBinder token) { + return getRequestIndex(token) != -1; + } + + /** + * Notifies the controller that the process with the specified {@code pid} has died. The + * controller will notify the listener of all changes to request status as a result of this + * operation. + */ + void handleProcessDied(int pid) { + if (mRequests.isEmpty()) { + return; + } + + mTmpRequestsToCancel.clear(); + OverrideRequest prevActiveRequest = getLast(mRequests); + for (OverrideRequest request : mRequests) { + if (request.getPid() == pid) { + mTmpRequestsToCancel.add(request); + } + } + + if (mStickyRequestsAllowed) { + // Do not cancel the requests now because sticky requests are allowed. These + // requests will be cancelled on a call to cancelStickyRequests(). + mStickyRequests.addAll(mTmpRequestsToCancel); + return; + } + + cancelRequestsLocked(mTmpRequestsToCancel); + } + + /** + * Notifies the controller that the base state has changed. The controller will notify the + * listener of all changes to request status as a result of this change. + * + * @return {@code true} if calling this method has lead to a new active request, {@code false} + * otherwise. + */ + boolean handleBaseStateChanged() { + if (mRequests.isEmpty()) { + return false; + } + + mTmpRequestsToCancel.clear(); + OverrideRequest prevActiveRequest = getLast(mRequests); + for (int i = 0; i < mRequests.size(); i++) { + OverrideRequest request = mRequests.get(i); + if ((request.getFlags() & DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES) != 0) { + mTmpRequestsToCancel.add(request); + } + } + + final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel); + return newActiveRequest; + } + + /** + * Notifies the controller that the set of supported states has changed. The controller will + * notify the listener of all changes to request status as a result of this change. + * + * @return {@code true} if calling this method has lead to a new active request, {@code false} + * otherwise. + */ + boolean handleNewSupportedStates(int[] newSupportedStates) { + if (mRequests.isEmpty()) { + return false; + } + + mTmpRequestsToCancel.clear(); + for (int i = 0; i < mRequests.size(); i++) { + OverrideRequest request = mRequests.get(i); + if (!contains(newSupportedStates, request.getRequestedState())) { + mTmpRequestsToCancel.add(request); + } + } + + final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel); + return newActiveRequest; + } + + void dumpInternal(PrintWriter pw) { + final int requestCount = mRequests.size(); + pw.println(); + pw.println("Override requests: size=" + requestCount); + for (int i = 0; i < requestCount; i++) { + OverrideRequest overrideRequest = mRequests.get(i); + int status = (i == requestCount - 1) ? STATUS_ACTIVE : STATUS_SUSPENDED; + pw.println(" " + i + ": mPid=" + overrideRequest.getPid() + + ", mRequestedState=" + overrideRequest.getRequestedState() + + ", mFlags=" + overrideRequest.getFlags() + + ", mStatus=" + statusToString(status)); + } + } + + /** + * Handles cancelling a set of requests. If the set of requests to cancel will lead to a new + * request becoming active this request will also be notified of its change in state. + * + * @return {@code true} if calling this method has lead to a new active request, {@code false} + * otherwise. + */ + private boolean cancelRequestsLocked(List<OverrideRequest> requestsToCancel) { + if (requestsToCancel.isEmpty()) { + return false; + } + + OverrideRequest prevActiveRequest = getLast(mRequests); + boolean causedNewRequestToBecomeActive = false; + mRequests.removeAll(requestsToCancel); + mStickyRequests.removeAll(requestsToCancel); + if (!mRequests.isEmpty()) { + OverrideRequest newActiveRequest = getLast(mRequests); + if (newActiveRequest != prevActiveRequest) { + mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE); + causedNewRequestToBecomeActive = true; + } + } + + for (int i = 0; i < requestsToCancel.size(); i++) { + mListener.onStatusChanged(requestsToCancel.get(i), STATUS_CANCELED); + } + return causedNewRequestToBecomeActive; + } + + private int getRequestIndex(@NonNull IBinder token) { + final int numberOfRequests = mRequests.size(); + if (numberOfRequests == 0) { + return -1; + } + + for (int i = 0; i < numberOfRequests; i++) { + OverrideRequest request = mRequests.get(i); + if (request.getToken() == token) { + return i; + } + } + return -1; + } + + @Nullable + private static <T> T getLast(List<T> list) { + return list.size() > 0 ? list.get(list.size() - 1) : null; + } + + private static boolean contains(int[] array, int value) { + for (int i = 0; i < array.length; i++) { + if (array[i] == value) { + return true; + } + } + return false; + } + + public interface StatusChangeListener { + /** + * Notifies the listener of a change in request status. If a change within the controller + * causes one request to become active and one to become either suspended or cancelled, this + * method is guaranteed to be called with the active request first before the suspended or + * cancelled request. + */ + void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus); + } +} diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java index 1acd5d097525..9dd2f8408c56 100644 --- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java +++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java @@ -111,8 +111,8 @@ class DeviceStateToLayoutMap { for (com.android.server.display.config.layout.Display d: l.getDisplay()) { layout.createDisplayLocked( DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()), - d.getIsDefault(), - d.getEnabled()); + d.isDefaultDisplay(), + d.isEnabled()); } } } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 35f29579b417..806bcc29f305 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -16,7 +16,9 @@ package com.android.server.display; +import android.annotation.Nullable; import android.content.Context; +import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayViewport; import android.os.IBinder; @@ -43,6 +45,7 @@ abstract class DisplayDevice { // The display device does not manage these properties itself, they are set by // the display manager service. The display device shouldn't really be looking at these. private int mCurrentLayerStack = -1; + private int mCurrentFlags = 0; private int mCurrentOrientation = -1; private Rect mCurrentLayerStackRect; private Rect mCurrentDisplayRect; @@ -104,6 +107,34 @@ abstract class DisplayDevice { } /** + * Returns the window token of the level of the WindowManager hierarchy to mirror, or null + * if layer mirroring by SurfaceFlinger should not be performed. + * For now, only used for mirroring started from MediaProjection. + */ + @Nullable + public IBinder getWindowTokenClientToMirrorLocked() { + return null; + } + + /** + * Updates the window token of the level of the level of the WindowManager hierarchy to mirror. + * If windowToken is null, then no layer mirroring by SurfaceFlinger to should be performed. + * For now, only used for mirroring started from MediaProjection. + */ + public void setWindowTokenClientToMirrorLocked(IBinder windowToken) { + } + + /** + * Returns the default size of the surface associated with the display, or null if the surface + * is not provided for layer mirroring by SurfaceFlinger. + * For now, only used for mirroring started from MediaProjection. + */ + @Nullable + public Point getDisplaySurfaceDefaultSize() { + return null; + } + + /** * Gets the name of the display device. * * @return The display device name. @@ -212,6 +243,19 @@ abstract class DisplayDevice { } /** + * Sets the display flags while in a transaction. + * + * Valid display flags: + * {@link SurfaceControl#DISPLAY_RECEIVES_INPUT} + */ + public final void setDisplayFlagsLocked(SurfaceControl.Transaction t, int flags) { + if (mCurrentFlags != flags) { + mCurrentFlags = flags; + t.setDisplayFlags(mDisplayToken, flags); + } + } + + /** * Sets the display projection while in a transaction. * * @param orientation defines the display's orientation @@ -298,6 +342,7 @@ abstract class DisplayDevice { pw.println("mUniqueId=" + mUniqueId); pw.println("mDisplayToken=" + mDisplayToken); pw.println("mCurrentLayerStack=" + mCurrentLayerStack); + pw.println("mCurrentFlags=" + mCurrentFlags); pw.println("mCurrentOrientation=" + mCurrentOrientation); pw.println("mCurrentLayerStackRect=" + mCurrentLayerStackRect); pw.println("mCurrentDisplayRect=" + mCurrentDisplayRect); diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java index 57f44864d2c0..2b52350f0634 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java +++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java @@ -123,6 +123,17 @@ class DisplayDeviceRepository implements DisplayAdapter.Listener { return null; } + // String uniqueId -> DisplayDevice object with that given uniqueId + public DisplayDevice getByUniqueIdLocked(@NonNull String uniqueId) { + for (int i = mDisplayDevices.size() - 1; i >= 0; i--) { + final DisplayDevice displayDevice = mDisplayDevices.get(i); + if (displayDevice.getUniqueId().equals(uniqueId)) { + return displayDevice; + } + } + return null; + } + private void handleDisplayDeviceAdded(DisplayDevice device) { synchronized (mSyncRoot) { DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 1dc83f6f829b..a432d5bf081c 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -202,7 +202,7 @@ public final class DisplayManagerService extends SystemService { private static final int MSG_DELIVER_DISPLAY_EVENT = 3; private static final int MSG_REQUEST_TRAVERSAL = 4; private static final int MSG_UPDATE_VIEWPORT = 5; - private static final int MSG_LOAD_BRIGHTNESS_CONFIGURATION = 6; + private static final int MSG_LOAD_BRIGHTNESS_CONFIGURATIONS = 6; private static final int MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE = 7; private static final int MSG_DELIVER_DISPLAY_GROUP_EVENT = 8; @@ -536,16 +536,29 @@ public final class DisplayManagerService extends SystemService { final int newUserId = to.getUserIdentifier(); final int userSerial = getUserManager().getUserSerialNumber(newUserId); synchronized (mSyncRoot) { - final DisplayPowerController displayPowerController = mDisplayPowerControllers.get( - Display.DEFAULT_DISPLAY); - if (mCurrentUserId != newUserId) { + boolean userSwitching = mCurrentUserId != newUserId; + if (userSwitching) { mCurrentUserId = newUserId; - BrightnessConfiguration config = - mPersistentDataStore.getBrightnessConfiguration(userSerial); - displayPowerController.setBrightnessConfiguration(config); - handleSettingsChange(); } - displayPowerController.onSwitchUser(newUserId); + mLogicalDisplayMapper.forEachLocked(logicalDisplay -> { + if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) { + return; + } + final DisplayPowerController dpc = mDisplayPowerControllers.get( + logicalDisplay.getDisplayIdLocked()); + if (dpc == null) { + return; + } + if (userSwitching) { + BrightnessConfiguration config = + getBrightnessConfigForDisplayWithPdsFallbackLocked( + logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(), + userSerial); + dpc.setBrightnessConfiguration(config); + } + dpc.onSwitchUser(newUserId); + }); + handleSettingsChange(); } } @@ -1328,6 +1341,13 @@ public final class DisplayManagerService extends SystemService { if (work != null) { mHandler.post(work); } + final int displayId = display.getDisplayIdLocked(); + DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); + if (dpc != null) { + dpc.onDisplayChanged(); + } + mPersistentDataStore.saveIfNeeded(); + mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATIONS); handleLogicalDisplayChangedLocked(display); } @@ -1436,24 +1456,42 @@ public final class DisplayManagerService extends SystemService { return mDisplayModeDirector.getModeSwitchingType(); } - private void setBrightnessConfigurationForUserInternal( - @Nullable BrightnessConfiguration c, @UserIdInt int userId, - @Nullable String packageName) { + private void setBrightnessConfigurationForDisplayInternal( + @Nullable BrightnessConfiguration c, String uniqueId, @UserIdInt int userId, + String packageName) { validateBrightnessConfiguration(c); final int userSerial = getUserManager().getUserSerialNumber(userId); synchronized (mSyncRoot) { try { - mPersistentDataStore.setBrightnessConfigurationForUser(c, userSerial, - packageName); + DisplayDevice displayDevice = mDisplayDeviceRepo.getByUniqueIdLocked(uniqueId); + if (displayDevice == null) { + return; + } + mPersistentDataStore.setBrightnessConfigurationForDisplayLocked(c, displayDevice, + userSerial, packageName); } finally { mPersistentDataStore.saveIfNeeded(); } - if (userId == mCurrentUserId) { - mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY).setBrightnessConfiguration(c); + if (userId != mCurrentUserId) { + return; + } + DisplayPowerController dpc = getDpcFromUniqueIdLocked(uniqueId); + if (dpc != null) { + dpc.setBrightnessConfiguration(c); } } } + private DisplayPowerController getDpcFromUniqueIdLocked(String uniqueId) { + final DisplayDevice displayDevice = mDisplayDeviceRepo.getByUniqueIdLocked(uniqueId); + final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayDevice); + if (logicalDisplay != null) { + final int displayId = logicalDisplay.getDisplayIdLocked(); + return mDisplayPowerControllers.get(displayId); + } + return null; + } + @VisibleForTesting void validateBrightnessConfiguration(BrightnessConfiguration config) { if (config == null) { @@ -1476,13 +1514,22 @@ public final class DisplayManagerService extends SystemService { return false; } - private void loadBrightnessConfiguration() { + private void loadBrightnessConfigurations() { + int userSerial = getUserManager().getUserSerialNumber(mContext.getUserId()); synchronized (mSyncRoot) { - final int userSerial = getUserManager().getUserSerialNumber(mCurrentUserId); - BrightnessConfiguration config = - mPersistentDataStore.getBrightnessConfiguration(userSerial); - mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY).setBrightnessConfiguration( - config); + mLogicalDisplayMapper.forEachLocked((logicalDisplay) -> { + final String uniqueId = + logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); + final BrightnessConfiguration config = + getBrightnessConfigForDisplayWithPdsFallbackLocked(uniqueId, userSerial); + if (config != null) { + final DisplayPowerController dpc = mDisplayPowerControllers.get( + logicalDisplay.getDisplayIdLocked()); + if (dpc != null) { + dpc.setBrightnessConfiguration(config); + } + } + }); } } @@ -1693,9 +1740,17 @@ public final class DisplayManagerService extends SystemService { return SurfaceControl.getDisplayedContentSample(token, maxFrames, timestamp); } - void resetBrightnessConfiguration() { - setBrightnessConfigurationForUserInternal(null, mContext.getUserId(), + void resetBrightnessConfigurations() { + mPersistentDataStore.setBrightnessConfigurationForUser(null, mContext.getUserId(), mContext.getPackageName()); + mLogicalDisplayMapper.forEachLocked((logicalDisplay -> { + if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) { + return; + } + final String uniqueId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); + setBrightnessConfigurationForDisplayInternal(null, uniqueId, mContext.getUserId(), + mContext.getPackageName()); + })); } void setAutoBrightnessLoggingEnabled(boolean enabled) { @@ -1762,10 +1817,13 @@ public final class DisplayManagerService extends SystemService { final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); final boolean ownContent = (info.flags & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0; + // Mirror the part of WM hierarchy that corresponds to the provided window token. + IBinder windowTokenClientToMirror = device.getWindowTokenClientToMirrorLocked(); + // Find the logical display that the display device is showing. // Certain displays only ever show their own content. LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); - if (!ownContent) { + if (!ownContent && windowTokenClientToMirror == null) { if (display != null && !display.hasContentLocked()) { // If the display does not have any content of its own, then // automatically mirror the requested logical display contents if possible. @@ -2005,9 +2063,6 @@ public final class DisplayManagerService extends SystemService { pw.println(); mLogicalDisplayMapper.dumpLocked(pw); - pw.println(); - mDisplayModeDirector.dump(pw); - final int callbackCount = mCallbacks.size(); pw.println(); pw.println("Callbacks: size=" + callbackCount); @@ -2030,6 +2085,8 @@ public final class DisplayManagerService extends SystemService { pw.println(); mPersistentDataStore.dump(pw); } + pw.println(); + mDisplayModeDirector.dump(pw); synchronized (mSyncDump) { mDumpInProgress = false; } @@ -2137,6 +2194,18 @@ public final class DisplayManagerService extends SystemService { return display == null ? null : display.getPrimaryDisplayDeviceLocked(); } + private BrightnessConfiguration getBrightnessConfigForDisplayWithPdsFallbackLocked( + String uniqueId, int userSerial) { + BrightnessConfiguration config = + mPersistentDataStore.getBrightnessConfigurationForDisplayLocked( + uniqueId, userSerial); + if (config == null) { + // Get from global configurations + config = mPersistentDataStore.getBrightnessConfiguration(userSerial); + } + return config; + } + private final class DisplayManagerHandler extends Handler { public DisplayManagerHandler(Looper looper) { super(looper, null, true /*async*/); @@ -2178,8 +2247,8 @@ public final class DisplayManagerService extends SystemService { break; } - case MSG_LOAD_BRIGHTNESS_CONFIGURATION: - loadBrightnessConfiguration(); + case MSG_LOAD_BRIGHTNESS_CONFIGURATIONS: + loadBrightnessConfigurations(); break; case MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE: @@ -2188,6 +2257,9 @@ public final class DisplayManagerService extends SystemService { int displayId = msg.arg1; final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); + if (display == null) { + break; + } uids = display.getPendingFrameRateOverrideUids(); display.clearPendingFrameRateOverrideUids(); } @@ -2801,21 +2873,49 @@ public final class DisplayManagerService extends SystemService { mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS, "Permission required to change the display brightness" - + " configuration of another user"); + + " configuration of another user"); } - if (packageName != null && !validatePackageName(getCallingUid(), packageName)) { - packageName = null; + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + mLogicalDisplayMapper.forEachLocked(logicalDisplay -> { + if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) { + return; + } + final DisplayDevice displayDevice = + logicalDisplay.getPrimaryDisplayDeviceLocked(); + setBrightnessConfigurationForDisplayInternal(c, displayDevice.getUniqueId(), + userId, packageName); + }); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call + public void setBrightnessConfigurationForDisplay(BrightnessConfiguration c, + String uniqueId, int userId, String packageName) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS, + "Permission required to change the display's brightness configuration"); + if (userId != UserHandle.getCallingUserId()) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS, + "Permission required to change the display brightness" + + " configuration of another user"); } final long token = Binder.clearCallingIdentity(); try { - setBrightnessConfigurationForUserInternal(c, userId, packageName); + setBrightnessConfigurationForDisplayInternal(c, uniqueId, userId, packageName); } finally { Binder.restoreCallingIdentity(token); } } @Override // Binder call - public BrightnessConfiguration getBrightnessConfigurationForUser(int userId) { + public BrightnessConfiguration getBrightnessConfigurationForDisplay(String uniqueId, + int userId) { mContext.enforceCallingOrSelfPermission( Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS, "Permission required to read the display's brightness configuration"); @@ -2826,14 +2926,19 @@ public final class DisplayManagerService extends SystemService { + " configuration of another user"); } final long token = Binder.clearCallingIdentity(); + final int userSerial = getUserManager().getUserSerialNumber(userId); try { - final int userSerial = getUserManager().getUserSerialNumber(userId); synchronized (mSyncRoot) { + // Get from per-display configurations BrightnessConfiguration config = - mPersistentDataStore.getBrightnessConfiguration(userSerial); + getBrightnessConfigForDisplayWithPdsFallbackLocked( + uniqueId, userSerial); if (config == null) { - config = mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY) - .getDefaultBrightnessConfiguration(); + // Get default configuration + DisplayPowerController dpc = getDpcFromUniqueIdLocked(uniqueId); + if (dpc != null) { + config = dpc.getDefaultBrightnessConfiguration(); + } } return config; } @@ -2842,6 +2947,21 @@ public final class DisplayManagerService extends SystemService { } } + + + @Override // Binder call + public BrightnessConfiguration getBrightnessConfigurationForUser(int userId) { + final String uniqueId; + synchronized (mSyncRoot) { + DisplayDevice displayDevice = mLogicalDisplayMapper.getDisplayLocked( + Display.DEFAULT_DISPLAY).getPrimaryDisplayDeviceLocked(); + uniqueId = displayDevice.getUniqueId(); + } + return getBrightnessConfigurationForDisplay(uniqueId, userId); + + + } + @Override // Binder call public BrightnessConfiguration getDefaultBrightnessConfiguration() { mContext.enforceCallingOrSelfPermission( @@ -3110,7 +3230,7 @@ public final class DisplayManagerService extends SystemService { initializeDisplayPowerControllersLocked(); } - mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATION); + mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATIONS); } @Override @@ -3338,6 +3458,40 @@ public final class DisplayManagerService extends SystemService { } return config.getRefreshRateLimitations(); } + + @Override + public IBinder getWindowTokenClientToMirror(int displayId) { + final DisplayDevice device; + synchronized (mSyncRoot) { + device = getDeviceForDisplayLocked(displayId); + if (device == null) { + return null; + } + } + return device.getWindowTokenClientToMirrorLocked(); + } + + @Override + public void setWindowTokenClientToMirror(int displayId, IBinder windowToken) { + synchronized (mSyncRoot) { + final DisplayDevice device = getDeviceForDisplayLocked(displayId); + if (device != null) { + device.setWindowTokenClientToMirrorLocked(windowToken); + } + } + } + + @Override + public Point getDisplaySurfaceDefaultSize(int displayId) { + final DisplayDevice device; + synchronized (mSyncRoot) { + device = getDeviceForDisplayLocked(displayId); + if (device == null) { + return null; + } + } + return device.getDisplaySurfaceDefaultSize(); + } } class DesiredDisplayModeSpecsObserver diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index 48edb73ac81d..158c8f06d13e 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -115,7 +115,7 @@ class DisplayManagerShellCommand extends ShellCommand { } private int resetBrightnessConfiguration() { - mService.resetBrightnessConfiguration(); + mService.resetBrightnessConfigurations(); return 0; } diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index c8f654c5f5c7..ae51dd787bf5 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -41,7 +41,6 @@ import android.os.IThermalEventListener; import android.os.IThermalService; import android.os.Looper; import android.os.Message; -import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -621,8 +620,8 @@ public class DisplayModeDirector { mHbmObserver.dumpLocked(pw); mSkinThermalStatusObserver.dumpLocked(pw); } - //mLock not needed to collect sensor dump - mSensorObserver.dumpLocked(pw); + + mSensorObserver.dump(pw); } private void updateVoteLocked(int priority, Vote vote) { @@ -2242,7 +2241,7 @@ public class DisplayModeDirector { } } - void dumpLocked(PrintWriter pw) { + void dump(PrintWriter pw) { pw.println(" SensorObserver"); pw.println(" mIsProxActive=" + mIsProxActive); pw.println(" mDozeStateByDisplay:"); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 81cbbff44778..c5363437f878 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -108,8 +108,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // screen state returns. Playing the animation can also be somewhat slow. private static final boolean USE_COLOR_FADE_ON_ANIMATION = false; - // The minimum reduction in brightness when dimmed. - private static final float SCREEN_DIM_MINIMUM_REDUCTION_FLOAT = 0.04f; private static final float SCREEN_ANIMATION_RATE_MINIMUM = 0.0f; private static final int COLOR_FADE_ON_ANIMATION_DURATION_MILLIS = 250; @@ -200,6 +198,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // The dim screen brightness. private final float mScreenBrightnessDimConfig; + // The minimum dim amount to use if the screen brightness is already below + // mScreenBrightnessDimConfig. + private final float mScreenBrightnessMinimumDimAmount; + private final float mScreenBrightnessDefault; // The minimum allowed brightness while in VR. @@ -378,6 +380,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private float mInitialAutoBrightness; // The controller for the automatic brightness level. + @Nullable private AutomaticBrightnessController mAutomaticBrightnessController; private Sensor mLightSensor; @@ -484,6 +487,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)); mScreenBrightnessDimConfig = clampAbsoluteBrightness( pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)); + mScreenBrightnessMinimumDimAmount = resources.getFloat( + com.android.internal.R.dimen.config_screenBrightnessMinimumDimAmountFloat); + // NORMAL SCREEN SETTINGS mScreenBrightnessDefault = clampAbsoluteBrightness( @@ -608,7 +614,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mPendingRbcOnOrChanged = strengthChanged || justActivated; // Reset model if strength changed OR rbc is turned off - if (strengthChanged || !justActivated && mAutomaticBrightnessController != null) { + if ((strengthChanged || !justActivated) && mAutomaticBrightnessController != null) { mAutomaticBrightnessController.resetShortTermModel(); } } @@ -739,13 +745,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final IBinder token = device.getDisplayTokenLocked(); final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); mHandler.post(() -> { - if (mDisplayDevice == device) { - return; + if (mDisplayDevice != device) { + mDisplayDevice = device; + mUniqueDisplayId = uniqueId; + mDisplayDeviceConfig = config; + loadFromDisplayDeviceConfig(token, info); + updatePowerState(); } - mDisplayDevice = device; - mUniqueDisplayId = uniqueId; - mDisplayDeviceConfig = config; - loadFromDisplayDeviceConfig(token, info); }); } @@ -1052,11 +1058,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } assert(state != Display.STATE_UNKNOWN); - // Initialize things the first time the power state is changed. - if (mustInitialize) { - initialize(state); - } - // Apply the proximity sensor. if (mProximitySensor != null) { if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) { @@ -1107,6 +1108,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call state = Display.STATE_OFF; } + // Initialize things the first time the power state is changed. + if (mustInitialize) { + initialize(state); + } + // Animate the screen state change unless already animating. // The transition may be deferred, so after this point we will use the // actual state instead of the desired one. @@ -1283,7 +1289,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) { if (brightnessState > PowerManager.BRIGHTNESS_MIN) { brightnessState = Math.max( - Math.min(brightnessState - SCREEN_DIM_MINIMUM_REDUCTION_FLOAT, + Math.min(brightnessState - mScreenBrightnessMinimumDimAmount, mScreenBrightnessDimConfig), PowerManager.BRIGHTNESS_MIN); mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED); @@ -1568,7 +1574,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call sendUpdatePowerStateLocked(); mHandler.post(mOnBrightnessChangeRunnable); // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern. - mAutomaticBrightnessController.update(); + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.update(); + } }, mContext); } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index a823f3e837f3..526e8e11f555 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -603,14 +603,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false))) { mInfo.flags |= DisplayDeviceInfo.FLAG_ROUND; } - if (res.getBoolean( - com.android.internal.R.bool.config_maskMainBuiltInDisplayCutout)) { - mInfo.flags |= DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT; - } - mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res, - mInfo.width, mInfo.height); - mInfo.roundedCorners = RoundedCorners.fromResources( - res, mInfo.width, mInfo.height); } else if (isBuiltIn) { mInfo.type = Display.TYPE_INTERNAL; mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL; @@ -642,6 +634,15 @@ final class LocalDisplayAdapter extends DisplayAdapter { } } + if (DisplayCutout.getMaskBuiltInDisplayCutout(res, mInfo.uniqueId)) { + mInfo.flags |= DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT; + } + mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res, + mInfo.uniqueId, mInfo.width, mInfo.height); + + mInfo.roundedCorners = RoundedCorners.fromResources( + res, mInfo.uniqueId, mInfo.width, mInfo.height); + if (mStaticDisplayInfo.isInternal) { mInfo.type = Display.TYPE_INTERNAL; mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL; diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 9acb4c8f471a..5186744d5c27 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -16,6 +16,8 @@ package com.android.server.display; +import static com.android.server.display.DisplayDeviceInfo.TOUCH_NONE; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -512,6 +514,11 @@ final class LogicalDisplay { boolean isBlanked) { // Set the layer stack. device.setLayerStackLocked(t, isBlanked ? BLANK_LAYER_STACK : mLayerStack); + // Also inform whether the device is the same one sent to inputflinger for its layerstack. + // TODO(b/188914255): Remove once input can dispatch against device vs layerstack. + device.setDisplayFlagsLocked(t, + device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE + ? SurfaceControl.DISPLAY_RECEIVES_INPUT : 0); // Set the color mode and allowed display mode. if (device == mPrimaryDisplayDevice) { diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 3364e5e148d1..4ff3db825003 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -22,6 +22,8 @@ import android.hardware.devicestate.DeviceStateManager; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.PowerManager; +import android.os.SystemClock; import android.os.SystemProperties; import android.text.TextUtils; import android.util.IndentingPrintWriter; @@ -68,7 +70,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { public static final int DISPLAY_GROUP_EVENT_CHANGED = 2; public static final int DISPLAY_GROUP_EVENT_REMOVED = 3; - private static final int TIMEOUT_STATE_TRANSITION_MILLIS = 500; + private static final int TIMEOUT_STATE_TRANSITION_MILLIS = 300; private static final int MSG_TRANSITION_TO_PENDING_DEVICE_STATE = 1; @@ -97,6 +99,11 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private final boolean mSupportsConcurrentInternalDisplays; /** + * Wake the device when transitioning into this device state. + */ + private final int mDeviceStateOnWhichToWakeUp; + + /** * Map of all logical displays indexed by logical display id. * Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache. * TODO: multi-display - Move the aforementioned comment? @@ -113,6 +120,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private final Listener mListener; private final DisplayManagerService.SyncRoot mSyncRoot; private final LogicalDisplayMapperHandler mHandler; + private final PowerManager mPowerManager; /** * Has an entry for every logical display that the rest of the system has been notified about. @@ -150,12 +158,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot, @NonNull Handler handler) { mSyncRoot = syncRoot; + mPowerManager = context.getSystemService(PowerManager.class); mHandler = new LogicalDisplayMapperHandler(handler.getLooper()); mDisplayDeviceRepo = repo; mListener = listener; mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false); mSupportsConcurrentInternalDisplays = context.getResources().getBoolean( com.android.internal.R.bool.config_supportsConcurrentInternalDisplays); + mDeviceStateOnWhichToWakeUp = context.getResources().getInteger( + com.android.internal.R.integer.config_deviceStateOnWhichToWakeUp); mDisplayDeviceRepo.addListener(this); mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(); } @@ -197,6 +208,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } public LogicalDisplay getDisplayLocked(DisplayDevice device) { + if (device == null) { + return null; + } final int count = mLogicalDisplays.size(); for (int i = 0; i < count; i++) { final LogicalDisplay display = mLogicalDisplays.valueAt(i); @@ -260,6 +274,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode); ipw.println("mCurrentLayout=" + mCurrentLayout); + ipw.println("mDeviceStateOnWhichToWakeUp=" + mDeviceStateOnWhichToWakeUp); final int logicalDisplayCount = mLogicalDisplays.size(); ipw.println(); @@ -277,7 +292,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } void setDeviceStateLocked(int state) { - Slog.i(TAG, "Requesting Transition to state: " + state); + final boolean isInteractive = mPowerManager.isInteractive(); + Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState + + ", interactive=" + isInteractive); // As part of a state transition, we may need to turn off some displays temporarily so that // the transition is smooth. Plus, on some devices, only one internal displays can be // on at a time. We use DISPLAY_PHASE_LAYOUT_TRANSITION to mark a display that needs to be @@ -286,8 +303,13 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION); } mPendingDeviceState = state; - if (areAllTransitioningDisplaysOffLocked()) { - // Nothing to wait on, we're good to go + final boolean wakeDevice = mPendingDeviceState == mDeviceStateOnWhichToWakeUp + && !isInteractive; + + // If all displays are off already, we can just transition here, unless the device is asleep + // and we plan on waking it up. In that case, fall through to the call to wakeUp, and defer + // the final transition until later once the device is awake. + if (areAllTransitioningDisplaysOffLocked() && !wakeDevice) { transitionToPendingStateLocked(); return; } @@ -298,6 +320,14 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // Send the transitioning phase updates to DisplayManager so that the displays can // start turning OFF in preparation for the new layout. updateLogicalDisplaysLocked(); + + if (wakeDevice) { + // We already told the displays to turn off, now we need to wake the device as + // we transition to this new state. We do it here so that the waking happens between the + // transition from one layout to another. + mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_UNFOLD_DEVICE, + "server.display:unfold"); + } mHandler.sendEmptyMessageDelayed(MSG_TRANSITION_TO_PENDING_DEVICE_STATE, TIMEOUT_STATE_TRANSITION_MILLIS); } @@ -424,6 +454,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { assignDisplayGroupLocked(display); mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED); + // The display is involved in a display layout transition } else if (updateState == UPDATE_STATE_TRANSITION) { mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION); @@ -614,14 +645,14 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // We consider a display-device as changing/transition if // 1) It's already marked as transitioning - // 2) It's going from enabled to disabled + // 2) It's going from enabled to disabled, or vice versa // 3) It's enabled, but it's mapped to a new logical display ID. To the user this // would look like apps moving from one screen to another since task-stacks stay // with the logical display [ID]. final boolean isTransitioning = (logicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) - || (wasEnabled && !willBeEnabled) - || (wasEnabled && deviceHasNewLogicalDisplayId); + || (wasEnabled != willBeEnabled) + || deviceHasNewLogicalDisplayId; if (isTransitioning) { setDisplayPhase(logicalDisplay, phase); diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java index c90ddf48a091..4b0d43b3d1d4 100644 --- a/services/core/java/com/android/server/display/PersistentDataStore.java +++ b/services/core/java/com/android/server/display/PersistentDataStore.java @@ -63,6 +63,15 @@ import java.util.Objects; * <display unique-id="XXXXXXX"> * <color-mode>0</color-mode> * <brightness-value>0</brightness-value> + * <brightness-configurations> + * <brightness-configuration user-serial="0" package-name="com.example" + * timestamp="1234"> + * <brightness-curve description="some text"> + * <brightness-point lux="0" nits="13.25"/> + * <brightness-point lux="20" nits="35.94"/> + * </brightness-curve> + * </brightness-configuration> + * </brightness-configurations> * </display> * </display-states> * <stable-device-values> @@ -120,7 +129,8 @@ final class PersistentDataStore { private final StableDeviceValues mStableDeviceValues = new StableDeviceValues(); // Brightness configuration by user - private BrightnessConfigurations mBrightnessConfigurations = new BrightnessConfigurations(); + private BrightnessConfigurations mGlobalBrightnessConfigurations = + new BrightnessConfigurations(); // True if the data has been loaded. private boolean mLoaded; @@ -293,18 +303,44 @@ final class PersistentDataStore { } } + // Used for testing & reset public void setBrightnessConfigurationForUser(BrightnessConfiguration c, int userSerial, @Nullable String packageName) { loadIfNeeded(); - if (mBrightnessConfigurations.setBrightnessConfigurationForUser(c, userSerial, + if (mGlobalBrightnessConfigurations.setBrightnessConfigurationForUser(c, userSerial, packageName)) { + setDirty(); } } + public boolean setBrightnessConfigurationForDisplayLocked(BrightnessConfiguration configuration, + DisplayDevice device, int userSerial, String packageName) { + if (device == null || !device.hasStableUniqueId()) { + return false; + } + DisplayState state = getDisplayState(device.getUniqueId(), /*createIfAbsent*/ true); + if (state.setBrightnessConfiguration(configuration, userSerial, packageName)) { + setDirty(); + return true; + } + return false; + } + + + public BrightnessConfiguration getBrightnessConfigurationForDisplayLocked( + String uniqueDisplayId, int userSerial) { + loadIfNeeded(); + DisplayState state = mDisplayStates.get(uniqueDisplayId); + if (state != null) { + return state.getBrightnessConfiguration(userSerial); + } + return null; + } + public BrightnessConfiguration getBrightnessConfiguration(int userSerial) { loadIfNeeded(); - return mBrightnessConfigurations.getBrightnessConfiguration(userSerial); + return mGlobalBrightnessConfigurations.getBrightnessConfiguration(userSerial); } private DisplayState getDisplayState(String uniqueId, boolean createIfAbsent) { @@ -391,7 +427,7 @@ final class PersistentDataStore { mStableDeviceValues.loadFromXml(parser); } if (parser.getName().equals(TAG_BRIGHTNESS_CONFIGURATIONS)) { - mBrightnessConfigurations.loadFromXml(parser); + mGlobalBrightnessConfigurations.loadFromXml(parser); } } } @@ -470,7 +506,7 @@ final class PersistentDataStore { mStableDeviceValues.saveToXml(serializer); serializer.endTag(null, TAG_STABLE_DEVICE_VALUES); serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS); - mBrightnessConfigurations.saveToXml(serializer); + mGlobalBrightnessConfigurations.saveToXml(serializer); serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS); serializer.endTag(null, TAG_DISPLAY_MANAGER_STATE); serializer.endDocument(); @@ -493,14 +529,18 @@ final class PersistentDataStore { } pw.println(" StableDeviceValues:"); mStableDeviceValues.dump(pw, " "); - pw.println(" BrightnessConfigurations:"); - mBrightnessConfigurations.dump(pw, " "); + pw.println(" GlobalBrightnessConfigurations:"); + mGlobalBrightnessConfigurations.dump(pw, " "); } private static final class DisplayState { private int mColorMode; private float mBrightness; + // Brightness configuration by user + private BrightnessConfigurations mDisplayBrightnessConfigurations = + new BrightnessConfigurations(); + public boolean setColorMode(int colorMode) { if (colorMode == mColorMode) { return false; @@ -525,6 +565,16 @@ final class PersistentDataStore { return mBrightness; } + public boolean setBrightnessConfiguration(BrightnessConfiguration configuration, + int userSerial, String packageName) { + mDisplayBrightnessConfigurations.setBrightnessConfigurationForUser( + configuration, userSerial, packageName); + return true; + } + + public BrightnessConfiguration getBrightnessConfiguration(int userSerial) { + return mDisplayBrightnessConfigurations.mConfigurations.get(userSerial); + } public void loadFromXml(TypedXmlPullParser parser) throws IOException, XmlPullParserException { @@ -540,6 +590,9 @@ final class PersistentDataStore { String brightness = parser.nextText(); mBrightness = Float.parseFloat(brightness); break; + case TAG_BRIGHTNESS_CONFIGURATIONS: + mDisplayBrightnessConfigurations.loadFromXml(parser); + break; } } } @@ -548,15 +601,21 @@ final class PersistentDataStore { serializer.startTag(null, TAG_COLOR_MODE); serializer.text(Integer.toString(mColorMode)); serializer.endTag(null, TAG_COLOR_MODE); + serializer.startTag(null, TAG_BRIGHTNESS_VALUE); serializer.text(Float.toString(mBrightness)); serializer.endTag(null, TAG_BRIGHTNESS_VALUE); + serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS); + mDisplayBrightnessConfigurations.saveToXml(serializer); + serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS); } public void dump(final PrintWriter pw, final String prefix) { pw.println(prefix + "ColorMode=" + mColorMode); pw.println(prefix + "BrightnessValue=" + mBrightness); + pw.println(prefix + "DisplayBrightnessConfigurations: "); + mDisplayBrightnessConfigurations.dump(pw, prefix); } } @@ -621,11 +680,11 @@ final class PersistentDataStore { private static final class BrightnessConfigurations { // Maps from a user ID to the users' given brightness configuration - private SparseArray<BrightnessConfiguration> mConfigurations; + private final SparseArray<BrightnessConfiguration> mConfigurations; // Timestamp of time the configuration was set. - private SparseLongArray mTimeStamps; + private final SparseLongArray mTimeStamps; // Package that set the configuration. - private SparseArray<String> mPackageNames; + private final SparseArray<String> mPackageNames; public BrightnessConfigurations() { mConfigurations = new SparseArray<>(); diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index b7931c8a8424..a59219265e2c 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -31,7 +31,9 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUST import static com.android.server.display.DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP; import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED; +import android.annotation.Nullable; import android.content.Context; +import android.graphics.Point; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; import android.media.projection.IMediaProjection; @@ -231,6 +233,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { private Display.Mode mMode; private boolean mIsDisplayOn; private int mDisplayIdToMirror; + private IBinder mWindowTokenClientToMirror; public VirtualDisplayDevice(IBinder displayToken, IBinder appToken, int ownerUid, String ownerPackageName, Surface surface, int flags, @@ -253,6 +256,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mUniqueIndex = uniqueIndex; mIsDisplayOn = surface != null; mDisplayIdToMirror = virtualDisplayConfig.getDisplayIdToMirror(); + mWindowTokenClientToMirror = virtualDisplayConfig.getWindowTokenClientToMirror(); } @Override @@ -282,6 +286,29 @@ public class VirtualDisplayAdapter extends DisplayAdapter { return mDisplayIdToMirror; } + @Override + @Nullable + public IBinder getWindowTokenClientToMirrorLocked() { + return mWindowTokenClientToMirror; + } + + @Override + public void setWindowTokenClientToMirrorLocked(IBinder windowToken) { + if (mWindowTokenClientToMirror != windowToken) { + mWindowTokenClientToMirror = windowToken; + sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED); + sendTraversalRequestLocked(); + } + } + + @Override + public Point getDisplaySurfaceDefaultSize() { + if (mSurface == null) { + return null; + } + return mSurface.getDefaultSize(); + } + @VisibleForTesting Surface getSurfaceLocked() { return mSurface; @@ -362,6 +389,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { pw.println("mDisplayState=" + Display.stateToString(mDisplayState)); pw.println("mStopped=" + mStopped); pw.println("mDisplayIdToMirror=" + mDisplayIdToMirror); + pw.println("mWindowTokenClientToMirror=" + mWindowTokenClientToMirror); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 6fb9e58a49d1..fae7e451b529 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -189,7 +189,7 @@ public class InputManagerService extends IInputManager.Stub private final InputManagerHandler mHandler; // Context cache used for loading pointer resources. - private Context mDisplayContext; + private Context mPointerIconDisplayContext; private final File mDoubleTouchGestureEnableFile; @@ -839,21 +839,31 @@ public class InputManagerService extends IInputManager.Stub throw new IllegalArgumentException("mode is invalid"); } if (ENABLE_PER_WINDOW_INPUT_ROTATION) { - if (event instanceof MotionEvent) { - final Context dispCtx = getContextForDisplay(event.getDisplayId()); - final Display display = dispCtx.getDisplay(); + // Motion events that are pointer events or relative mouse events will need to have the + // inverse display rotation applied to them. + if (event instanceof MotionEvent + && (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER) + || event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE))) { + Context displayContext = getContextForDisplay(event.getDisplayId()); + if (displayContext == null) { + displayContext = Objects.requireNonNull( + getContextForDisplay(Display.DEFAULT_DISPLAY)); + } + final Display display = displayContext.getDisplay(); final int rotation = display.getRotation(); if (rotation != ROTATION_0) { final MotionEvent motion = (MotionEvent) event; // Injections are currently expected to be in the space of the injector (ie. - // usually assumed to be post-rotated). Thus we need to unrotate into raw + // usually assumed to be post-rotated). Thus we need to un-rotate into raw // input coordinates for dispatch. final Point sz = new Point(); - display.getRealSize(sz); - if ((rotation % 2) != 0) { - final int tmpX = sz.x; - sz.x = sz.y; - sz.y = tmpX; + if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { + display.getRealSize(sz); + if ((rotation % 2) != 0) { + final int tmpX = sz.x; + sz.x = sz.y; + sz.y = tmpX; + } } motion.applyTransform(MotionEvent.createRotateMatrix( (4 - rotation), sz.x, sz.y)); @@ -890,6 +900,7 @@ public class InputManagerService extends IInputManager.Stub @Override // Binder call public VerifiedInputEvent verifyInputEvent(InputEvent event) { + Objects.requireNonNull(event, "event must not be null"); return nativeVerifyInputEvent(mPtr, event); } @@ -1742,6 +1753,11 @@ public class InputManagerService extends IInputManager.Stub /** Clean up input window handles of the given display. */ public void onDisplayRemoved(int displayId) { + if (mPointerIconDisplayContext != null + && mPointerIconDisplayContext.getDisplay().getDisplayId() == displayId) { + mPointerIconDisplayContext = null; + } + nativeDisplayRemoved(mPtr, displayId); } @@ -2971,24 +2987,43 @@ public class InputManagerService extends IInputManager.Stub // Native callback. private PointerIcon getPointerIcon(int displayId) { - return PointerIcon.getDefaultIcon(getContextForDisplay(displayId)); + return PointerIcon.getDefaultIcon(getContextForPointerIcon(displayId)); } - private Context getContextForDisplay(int displayId) { - if (mDisplayContext != null && mDisplayContext.getDisplay().getDisplayId() == displayId) { - return mDisplayContext; + @NonNull + private Context getContextForPointerIcon(int displayId) { + if (mPointerIconDisplayContext != null + && mPointerIconDisplayContext.getDisplay().getDisplayId() == displayId) { + return mPointerIconDisplayContext; + } + + // Create and cache context for non-default display. + mPointerIconDisplayContext = getContextForDisplay(displayId); + + // Fall back to default display if the requested displayId does not exist. + if (mPointerIconDisplayContext == null) { + mPointerIconDisplayContext = getContextForDisplay(Display.DEFAULT_DISPLAY); } + return mPointerIconDisplayContext; + } + @Nullable + private Context getContextForDisplay(int displayId) { + if (displayId == Display.INVALID_DISPLAY) { + return null; + } if (mContext.getDisplay().getDisplayId() == displayId) { - mDisplayContext = mContext; - return mDisplayContext; + return mContext; } - // Create and cache context for non-default display. - final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); + final DisplayManager displayManager = Objects.requireNonNull( + mContext.getSystemService(DisplayManager.class)); final Display display = displayManager.getDisplay(displayId); - mDisplayContext = mContext.createDisplayContext(display); - return mDisplayContext; + if (display == null) { + return null; + } + + return mContext.createDisplayContext(display); } // Native callback. diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 3e52f5e07e62..1516739de99f 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -542,9 +542,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ private InputMethodSubtype mCurrentSubtype; - // Was the keyguard locked when this client became current? - private boolean mCurClientInKeyguard; - /** * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController} */ @@ -2356,19 +2353,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (displayIdToShowIme == INVALID_DISPLAY) { mImeHiddenByDisplayPolicy = true; + hideCurrentInputLocked(mCurFocusedWindow, 0, null, + SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE); return InputBindResult.NO_IME; } mImeHiddenByDisplayPolicy = false; if (mCurClient != cs) { - // Was the keyguard locked when switching over to the new client? - mCurClientInKeyguard = isKeyguardLocked(); // If the client is changing, we need to switch over to the new // one. unbindCurrentClientLocked(UnbindReason.SWITCH_CLIENT); - if (DEBUG) Slog.v(TAG, "switching to client: client=" - + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard); - // If the screen is on, inform the new client it is active if (mIsInteractive) { scheduleSetActiveToClient(cs, true /* active */, false /* fullscreen */, @@ -2528,9 +2522,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); // Dispatch display id for InputMethodService to update context display. - executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOOO( - MSG_INITIALIZE_IME, mCurTokenDisplayId, mCurMethod, mCurToken, - mMethodMap.get(mCurMethodId).getConfigChanges())); + executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(MSG_INITIALIZE_IME, + mMethodMap.get(mCurMethodId).getConfigChanges(), mCurMethod, mCurToken)); scheduleNotifyImeUidToAudioService(mCurMethodUid); if (mCurClient != null) { clearClientSessionLocked(mCurClient); @@ -2837,7 +2830,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return; } final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken); - if (targetWindow != null && mLastImeTargetWindow != targetWindow) { + if (targetWindow != null) { mWindowManagerInternal.updateInputMethodTargetWindow(token, targetWindow); } mLastImeTargetWindow = targetWindow; @@ -2874,12 +2867,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // all updateSystemUi happens on system previlege. final long ident = Binder.clearCallingIdentity(); try { - // apply policy for binder calls - if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) { - vis = 0; - } if (!mCurPerceptible) { - vis &= ~InputMethodService.IME_VISIBLE; + if ((vis & InputMethodService.IME_VISIBLE) != 0) { + vis &= ~InputMethodService.IME_VISIBLE; + vis |= InputMethodService.IME_VISIBLE_IMPERCEPTIBLE; + } + } else { + vis &= ~InputMethodService.IME_VISIBLE_IMPERCEPTIBLE; } // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked(). final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis); @@ -4121,6 +4115,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + /** Called right after {@link IInputMethod#showSoftInput}. */ + private void onShowHideSoftInputRequested(boolean show, IBinder requestToken, + @SoftInputShowHideReason int reason) { + final WindowManagerInternal.ImeTargetInfo info = + mWindowManagerInternal.onToggleImeRequested( + show, mCurFocusedWindow, requestToken, mCurTokenDisplayId); + mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry( + mCurFocusedWindowClient, mCurAttribute, info.focusedWindowName, + mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode, + info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName)); + } + @BinderThread private void hideMySoftInput(@NonNull IBinder token, int flags) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput"); @@ -4239,18 +4245,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput(" + args.arg3 + ", " + msg.arg1 + ", " + args.arg2 + ") for reason: " + InputMethodDebug.softInputDisplayReasonToString(reason)); + final IBinder token = (IBinder) args.arg3; ((IInputMethod) args.arg1).showSoftInput( - (IBinder) args.arg3, msg.arg1, (ResultReceiver) args.arg2); - mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry( - mCurFocusedWindowClient, mCurAttribute, - mWindowManagerInternal.getWindowName(mCurFocusedWindow), - mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode, - mWindowManagerInternal.getWindowName( - mShowRequestWindowMap.get(args.arg3)), - mWindowManagerInternal.getImeControlTargetNameForLogging( - mCurTokenDisplayId), - mWindowManagerInternal.getImeTargetNameForLogging( - mCurTokenDisplayId))); + token, msg.arg1 /* flags */, (ResultReceiver) args.arg2); + final IBinder requestToken = mShowRequestWindowMap.get(token); + onShowHideSoftInputRequested(true /* show */, requestToken, reason); } catch (RemoteException e) { } args.recycle(); @@ -4262,18 +4261,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, " + args.arg3 + ", " + args.arg2 + ") for reason: " + InputMethodDebug.softInputDisplayReasonToString(reason)); + final IBinder token = (IBinder) args.arg3; ((IInputMethod)args.arg1).hideSoftInput( - (IBinder) args.arg3, 0, (ResultReceiver)args.arg2); - mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry( - mCurFocusedWindowClient, mCurAttribute, - mWindowManagerInternal.getWindowName(mCurFocusedWindow), - mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode, - mWindowManagerInternal.getWindowName( - mHideRequestWindowMap.get(args.arg3)), - mWindowManagerInternal.getImeControlTargetNameForLogging( - mCurTokenDisplayId), - mWindowManagerInternal.getImeTargetNameForLogging( - mCurTokenDisplayId))); + token, 0 /* flags */, (ResultReceiver) args.arg2); + final IBinder requestToken = mHideRequestWindowMap.get(token); + onShowHideSoftInputRequested(false /* show */, requestToken, reason); } catch (RemoteException e) { } args.recycle(); @@ -4290,12 +4282,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub try { if (DEBUG) { Slog.v(TAG, "Sending attach of token: " + args.arg2 + " for display: " - + msg.arg1); + + mCurTokenDisplayId); } final IBinder token = (IBinder) args.arg2; - ((IInputMethod) args.arg1).initializeInternal(token, msg.arg1, - new InputMethodPrivilegedOperationsImpl(this, token), - (int) args.arg3); + ((IInputMethod) args.arg1).initializeInternal(token, + new InputMethodPrivilegedOperationsImpl(this, token), msg.arg1); } catch (RemoteException e) { } args.recycle(); diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index aa4fa7c6f470..4a41cb6d81e2 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -917,7 +917,7 @@ public final class MultiClientInputMethodManagerService { .putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( context, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), - PendingIntent.FLAG_MUTABLE)); + PendingIntent.FLAG_IMMUTABLE)); // Note: Instead of re-dispatching callback from the main thread to the worker thread // where OnWorkerThreadCallback is running, we pass the Handler object here so that diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java index a52c9cefb27d..5093f5dee55e 100644 --- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java +++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java @@ -131,8 +131,8 @@ public class GeofenceManager extends return mPermitted; } - boolean onLocationPermissionsChanged(String packageName) { - if (getIdentity().getPackageName().equals(packageName)) { + boolean onLocationPermissionsChanged(@Nullable String packageName) { + if (packageName == null || getIdentity().getPackageName().equals(packageName)) { return onLocationPermissionsChanged(); } @@ -242,7 +242,7 @@ public class GeofenceManager extends mLocationPermissionsListener = new LocationPermissionsHelper.LocationPermissionsListener() { @Override - public void onLocationPermissionsChanged(String packageName) { + public void onLocationPermissionsChanged(@Nullable String packageName) { GeofenceManager.this.onLocationPermissionsChanged(packageName); } @@ -494,7 +494,7 @@ public class GeofenceManager extends updateRegistrations(registration -> registration.getIdentity().getUserId() == userId); } - void onLocationPermissionsChanged(String packageName) { + void onLocationPermissionsChanged(@Nullable String packageName) { updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName)); } diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java index 5e6ae68c02f2..a54047665aba 100644 --- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java +++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java @@ -119,8 +119,8 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter */ protected void onGnssListenerUnregister() {} - boolean onLocationPermissionsChanged(String packageName) { - if (getIdentity().getPackageName().equals(packageName)) { + boolean onLocationPermissionsChanged(@Nullable String packageName) { + if (packageName == null || getIdentity().getPackageName().equals(packageName)) { return onLocationPermissionsChanged(); } @@ -197,7 +197,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter mLocationPermissionsListener = new LocationPermissionsHelper.LocationPermissionsListener() { @Override - public void onLocationPermissionsChanged(String packageName) { + public void onLocationPermissionsChanged(@Nullable String packageName) { GnssListenerMultiplexer.this.onLocationPermissionsChanged(packageName); } @@ -390,7 +390,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter updateRegistrations(registration -> registration.getIdentity().getUserId() == userId); } - private void onLocationPermissionsChanged(String packageName) { + private void onLocationPermissionsChanged(@Nullable String packageName) { updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName)); } diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java index 8460d6797543..1781588b0ba2 100644 --- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java @@ -52,8 +52,6 @@ public final class GnssMeasurementsProvider extends private class GnssMeasurementListenerRegistration extends GnssListenerRegistration { - private static final String GNSS_MEASUREMENTS_BUCKET = "gnss_measurement"; - protected GnssMeasurementListenerRegistration( @Nullable GnssMeasurementRequest request, CallerIdentity callerIdentity, @@ -70,15 +68,13 @@ public final class GnssMeasurementsProvider extends @Nullable @Override protected void onActive() { - mLocationAttributionHelper.reportHighPowerLocationStart( - getIdentity(), GNSS_MEASUREMENTS_BUCKET, getKey()); + mLocationAttributionHelper.reportHighPowerLocationStart(getIdentity()); } @Nullable @Override protected void onInactive() { - mLocationAttributionHelper.reportHighPowerLocationStop( - getIdentity(), GNSS_MEASUREMENTS_BUCKET, getKey()); + mLocationAttributionHelper.reportHighPowerLocationStop(getIdentity()); } } diff --git a/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java b/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java index 5cb360be819a..483875230ac4 100644 --- a/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java +++ b/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java @@ -24,55 +24,23 @@ import static com.android.server.location.LocationManagerService.TAG; import android.location.util.identity.CallerIdentity; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.util.Map; -import java.util.Objects; -import java.util.Set; /** * Helps manage appop monitoring for multiple location clients. */ public class LocationAttributionHelper { - private static class BucketKey { - private final String mBucket; - private final Object mKey; - - private BucketKey(String bucket, Object key) { - mBucket = Objects.requireNonNull(bucket); - mKey = Objects.requireNonNull(key); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - BucketKey that = (BucketKey) o; - return mBucket.equals(that.mBucket) - && mKey.equals(that.mKey); - } - - @Override - public int hashCode() { - return Objects.hash(mBucket, mKey); - } - } - private final AppOpsHelper mAppOpsHelper; @GuardedBy("this") - private final Map<CallerIdentity, Set<BucketKey>> mAttributions; + private final Map<CallerIdentity, Integer> mAttributions; @GuardedBy("this") - private final Map<CallerIdentity, Set<BucketKey>> mHighPowerAttributions; + private final Map<CallerIdentity, Integer> mHighPowerAttributions; public LocationAttributionHelper(AppOpsHelper appOpsHelper) { mAppOpsHelper = appOpsHelper; @@ -84,15 +52,16 @@ public class LocationAttributionHelper { /** * Report normal location usage for the given caller in the given bucket, with a unique key. */ - public synchronized void reportLocationStart(CallerIdentity identity, String bucket, - Object key) { - Set<BucketKey> keySet = mAttributions.computeIfAbsent(identity, - i -> new ArraySet<>()); - boolean empty = keySet.isEmpty(); - if (keySet.add(new BucketKey(bucket, key)) && empty) { - if (!mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)) { - mAttributions.remove(identity); + public synchronized void reportLocationStart(CallerIdentity identity) { + identity = CallerIdentity.forAggregation(identity); + + int count = mAttributions.getOrDefault(identity, 0); + if (count == 0) { + if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)) { + mAttributions.put(identity, 1); } + } else { + mAttributions.put(identity, count + 1); } } @@ -100,13 +69,15 @@ public class LocationAttributionHelper { * Report normal location usage has stopped for the given caller in the given bucket, with a * unique key. */ - public synchronized void reportLocationStop(CallerIdentity identity, String bucket, - Object key) { - Set<BucketKey> keySet = mAttributions.get(identity); - if (keySet != null && keySet.remove(new BucketKey(bucket, key)) - && keySet.isEmpty()) { + public synchronized void reportLocationStop(CallerIdentity identity) { + identity = CallerIdentity.forAggregation(identity); + + int count = mAttributions.getOrDefault(identity, 0); + if (count == 1) { mAttributions.remove(identity); mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, identity); + } else if (count > 1) { + mAttributions.put(identity, count - 1); } } @@ -114,19 +85,19 @@ public class LocationAttributionHelper { * Report high power location usage for the given caller in the given bucket, with a unique * key. */ - public synchronized void reportHighPowerLocationStart(CallerIdentity identity, String bucket, - Object key) { - Set<BucketKey> keySet = mHighPowerAttributions.computeIfAbsent(identity, - i -> new ArraySet<>()); - boolean empty = keySet.isEmpty(); - if (keySet.add(new BucketKey(bucket, key)) && empty) { + public synchronized void reportHighPowerLocationStart(CallerIdentity identity) { + identity = CallerIdentity.forAggregation(identity); + + int count = mHighPowerAttributions.getOrDefault(identity, 0); + if (count == 0) { + if (D) { + Log.v(TAG, "starting high power location attribution for " + identity); + } if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, identity)) { - if (D) { - Log.v(TAG, "starting high power location attribution for " + identity); - } - } else { - mHighPowerAttributions.remove(identity); + mHighPowerAttributions.put(identity, 1); } + } else { + mHighPowerAttributions.put(identity, count + 1); } } @@ -134,16 +105,18 @@ public class LocationAttributionHelper { * Report high power location usage has stopped for the given caller in the given bucket, * with a unique key. */ - public synchronized void reportHighPowerLocationStop(CallerIdentity identity, String bucket, - Object key) { - Set<BucketKey> keySet = mHighPowerAttributions.get(identity); - if (keySet != null && keySet.remove(new BucketKey(bucket, key)) - && keySet.isEmpty()) { + public synchronized void reportHighPowerLocationStop(CallerIdentity identity) { + identity = CallerIdentity.forAggregation(identity); + + int count = mHighPowerAttributions.getOrDefault(identity, 0); + if (count == 1) { if (D) { Log.v(TAG, "stopping high power location attribution for " + identity); } mHighPowerAttributions.remove(identity); mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, identity); + } else if (count > 1) { + mHighPowerAttributions.put(identity, count - 1); } } } diff --git a/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java b/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java index 2df21017156d..557ecda2ef4c 100644 --- a/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java +++ b/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java @@ -18,6 +18,7 @@ package com.android.server.location.injector; import static com.android.server.location.LocationPermissions.PERMISSION_NONE; +import android.annotation.Nullable; import android.location.util.identity.CallerIdentity; import com.android.server.location.LocationPermissions; @@ -36,9 +37,10 @@ public abstract class LocationPermissionsHelper { public interface LocationPermissionsListener { /** - * Called when something has changed about location permissions for the given package. + * Called when something has changed about location permissions for the given package. A + * null package indicates this affects every package. */ - void onLocationPermissionsChanged(String packageName); + void onLocationPermissionsChanged(@Nullable String packageName); /** * Called when something has changed about location permissions for the given uid. diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 345dc217110b..9ed63b5ce2da 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -31,6 +31,7 @@ import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY; import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF; import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF; +import static android.os.UserHandle.USER_CURRENT; import static com.android.server.location.LocationManagerService.D; import static com.android.server.location.LocationManagerService.TAG; @@ -177,7 +178,7 @@ public class LocationProviderManager extends protected interface LocationTransport { void deliverOnLocationChanged(LocationResult locationResult, - @Nullable Runnable onCompleteCallback) throws Exception; + @Nullable IRemoteCallback onCompleteCallback) throws Exception; void deliverOnFlushComplete(int requestCode) throws Exception; } @@ -197,9 +198,8 @@ public class LocationProviderManager extends @Override public void deliverOnLocationChanged(LocationResult locationResult, - @Nullable Runnable onCompleteCallback) throws RemoteException { - mListener.onLocationChanged(locationResult.asList(), - SingleUseCallback.wrap(onCompleteCallback)); + @Nullable IRemoteCallback onCompleteCallback) throws RemoteException { + mListener.onLocationChanged(locationResult.asList(), onCompleteCallback); } @Override @@ -227,7 +227,7 @@ public class LocationProviderManager extends @Override public void deliverOnLocationChanged(LocationResult locationResult, - @Nullable Runnable onCompleteCallback) + @Nullable IRemoteCallback onCompleteCallback) throws PendingIntent.CanceledException { BroadcastOptions options = BroadcastOptions.makeBasic(); options.setDontSendToRestrictedApps(true); @@ -243,20 +243,34 @@ public class LocationProviderManager extends intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0])); } + PendingIntent.OnFinished onFinished = null; + // send() SHOULD only run the completion callback if it completes successfully. however, - // b/199464864 (which could not be fixed in the S timeframe) means that it's possible + // b/201299281 (which could not be fixed in the S timeframe) means that it's possible // for send() to throw an exception AND run the completion callback. if this happens, we // would over-release the wakelock... we take matters into our own hands to ensure that // the completion callback can only be run if send() completes successfully. this means // the completion callback may be run inline - but as we've never specified what thread // the callback is run on, this is fine. - GatedCallback gatedCallback = new GatedCallback(onCompleteCallback); + GatedCallback gatedCallback; + if (onCompleteCallback != null) { + gatedCallback = new GatedCallback(() -> { + try { + onCompleteCallback.sendResult(null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + }); + onFinished = (pI, i, rC, rD, rE) -> gatedCallback.run(); + } else { + gatedCallback = new GatedCallback(null); + } mPendingIntent.send( mContext, 0, intent, - (pI, i, rC, rD, rE) -> gatedCallback.run(), + onFinished, null, null, options.toBundle()); @@ -293,7 +307,7 @@ public class LocationProviderManager extends @Override public void deliverOnLocationChanged(@Nullable LocationResult locationResult, - @Nullable Runnable onCompleteCallback) + @Nullable IRemoteCallback onCompleteCallback) throws RemoteException { // ILocationCallback doesn't currently support completion callbacks Preconditions.checkState(onCompleteCallback == null); @@ -398,7 +412,7 @@ public class LocationProviderManager extends EVENT_LOG.logProviderClientActive(mName, getIdentity()); if (!getRequest().isHiddenFromAppOps()) { - mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey()); + mLocationAttributionHelper.reportLocationStart(getIdentity()); } onHighPowerUsageChanged(); @@ -413,7 +427,7 @@ public class LocationProviderManager extends onHighPowerUsageChanged(); if (!getRequest().isHiddenFromAppOps()) { - mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey()); + mLocationAttributionHelper.reportLocationStop(getIdentity()); } onProviderListenerInactive(); @@ -488,10 +502,10 @@ public class LocationProviderManager extends if (!getRequest().isHiddenFromAppOps()) { if (mIsUsingHighPower) { mLocationAttributionHelper.reportHighPowerLocationStart( - getIdentity(), getName(), getKey()); + getIdentity()); } else { mLocationAttributionHelper.reportHighPowerLocationStop( - getIdentity(), getName(), getKey()); + getIdentity()); } } } @@ -514,8 +528,8 @@ public class LocationProviderManager extends } @GuardedBy("mLock") - final boolean onLocationPermissionsChanged(String packageName) { - if (getIdentity().getPackageName().equals(packageName)) { + final boolean onLocationPermissionsChanged(@Nullable String packageName) { + if (packageName == null || getIdentity().getPackageName().equals(packageName)) { return onLocationPermissionsChanged(); } @@ -714,6 +728,13 @@ public class LocationProviderManager extends final PowerManager.WakeLock mWakeLock; + // b/206340085 - if we allocate a new wakelock releaser object for every delivery we + // increase the risk of resource starvation. if a client stops processing deliveries the + // system server binder allocation pool will be starved as we continue to queue up + // deliveries, each with a new allocation. in order to mitigate this, we use a single + // releaser object per registration rather than per delivery. + final ExternalWakeLockReleaser mWakeLockReleaser; + private volatile ProviderTransport mProviderTransport; private int mNumLocationsDelivered = 0; private long mExpirationRealtimeMs = Long.MAX_VALUE; @@ -727,6 +748,7 @@ public class LocationProviderManager extends .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); mWakeLock.setReferenceCounted(true); mWakeLock.setWorkSource(request.getWorkSource()); + mWakeLockReleaser = new ExternalWakeLockReleaser(identity, mWakeLock); } @Override @@ -872,6 +894,10 @@ public class LocationProviderManager extends MAX_FASTEST_INTERVAL_JITTER_MS); if (deltaMs < getRequest().getMinUpdateIntervalMillis() - maxJitterMs) { + if (D) { + Log.v(TAG, mName + " provider registration " + getIdentity() + + " dropped delivery - too fast"); + } return false; } @@ -881,6 +907,10 @@ public class LocationProviderManager extends if (smallestDisplacementM > 0.0 && location.distanceTo( mPreviousLocation) <= smallestDisplacementM) { + if (D) { + Log.v(TAG, mName + " provider registration " + getIdentity() + + " dropped delivery - too close"); + } return false; } } @@ -898,7 +928,8 @@ public class LocationProviderManager extends if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(getPermissionLevel()), getIdentity())) { if (D) { - Log.w(TAG, "noteOp denied for " + getIdentity()); + Log.w(TAG, + mName + " provider registration " + getIdentity() + " noteOp denied"); } return null; } @@ -911,18 +942,21 @@ public class LocationProviderManager extends @Override public void onPreExecute() { mUseWakeLock = false; - final int size = locationResult.size(); - for (int i = 0; i < size; ++i) { - if (!locationResult.get(i).isMock()) { - mUseWakeLock = true; - break; + + // don't acquire a wakelock for passive requests or for mock locations + if (getRequest().getIntervalMillis() != LocationRequest.PASSIVE_INTERVAL) { + final int size = locationResult.size(); + for (int i = 0; i < size; ++i) { + if (!locationResult.get(i).isMock()) { + mUseWakeLock = true; + break; + } } } // update last delivered location setLastDeliveredLocation(locationResult.getLastLocation()); - // don't acquire a wakelock for mock locations to prevent abuse if (mUseWakeLock) { mWakeLock.acquire(WAKELOCK_TIMEOUT_MS); } @@ -940,7 +974,7 @@ public class LocationProviderManager extends } listener.deliverOnLocationChanged(deliverLocationResult, - mUseWakeLock ? mWakeLock::release : null); + mUseWakeLock ? mWakeLockReleaser : null); EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(), getIdentity()); } @@ -1339,7 +1373,7 @@ public class LocationProviderManager extends private final LocationPermissionsListener mLocationPermissionsListener = new LocationPermissionsListener() { @Override - public void onLocationPermissionsChanged(String packageName) { + public void onLocationPermissionsChanged(@Nullable String packageName) { LocationProviderManager.this.onLocationPermissionsChanged(packageName); } @@ -1479,7 +1513,7 @@ public class LocationProviderManager extends public boolean isEnabled(int userId) { if (userId == UserHandle.USER_NULL) { return false; - } else if (userId == UserHandle.USER_CURRENT) { + } else if (userId == USER_CURRENT) { return isEnabled(mUserHelper.getCurrentUserId()); } @@ -1652,7 +1686,7 @@ public class LocationProviderManager extends } } return lastLocation; - } else if (userId == UserHandle.USER_CURRENT) { + } else if (userId == USER_CURRENT) { return getLastLocationUnsafe(mUserHelper.getCurrentUserId(), permissionLevel, isBypass, maximumAgeMs); } @@ -1697,7 +1731,7 @@ public class LocationProviderManager extends setLastLocation(location, runningUserIds[i]); } return; - } else if (userId == UserHandle.USER_CURRENT) { + } else if (userId == USER_CURRENT) { setLastLocation(location, mUserHelper.getCurrentUserId()); return; } @@ -1996,6 +2030,11 @@ public class LocationProviderManager extends + TimeUtils.formatDuration(delayMs)); } + if (mDelayedRegister != null) { + mAlarmHelper.cancel(mDelayedRegister); + mDelayedRegister = null; + } + mDelayedRegister = new OnAlarmListener() { @Override public void onAlarm() { @@ -2327,7 +2366,7 @@ public class LocationProviderManager extends } } - private void onLocationPermissionsChanged(String packageName) { + private void onLocationPermissionsChanged(@Nullable String packageName) { synchronized (mLock) { updateRegistrations( registration -> registration.onLocationPermissionsChanged(packageName)); @@ -2375,13 +2414,13 @@ public class LocationProviderManager extends filtered = locationResult.filter(location -> { if (!location.isMock()) { if (location.getLatitude() == 0 && location.getLongitude() == 0) { - Log.w(TAG, "blocking 0,0 location from " + mName + " provider"); + Log.e(TAG, "blocking 0,0 location from " + mName + " provider"); return false; } } if (!location.isComplete()) { - Log.w(TAG, "blocking incomplete location from " + mName + " provider"); + Log.e(TAG, "blocking incomplete location from " + mName + " provider"); return false; } @@ -2399,6 +2438,12 @@ public class LocationProviderManager extends filtered = locationResult; } + Location last = getLastLocationUnsafe(USER_CURRENT, PERMISSION_FINE, true, Long.MAX_VALUE); + if (last != null && locationResult.get(0).getElapsedRealtimeNanos() + < last.getElapsedRealtimeNanos()) { + Log.e(TAG, "non-monotonic location received from " + mName + " provider"); + } + // update last location setLastLocation(filtered.getLastLocation(), UserHandle.USER_ALL); @@ -2753,7 +2798,7 @@ public class LocationProviderManager extends @GuardedBy("this") private boolean mRun; - GatedCallback(Runnable callback) { + GatedCallback(@Nullable Runnable callback) { mCallback = callback; } @@ -2788,4 +2833,27 @@ public class LocationProviderManager extends } } } + + private static class ExternalWakeLockReleaser extends IRemoteCallback.Stub { + + private final CallerIdentity mIdentity; + private final PowerManager.WakeLock mWakeLock; + + ExternalWakeLockReleaser(CallerIdentity identity, PowerManager.WakeLock wakeLock) { + mIdentity = identity; + mWakeLock = Objects.requireNonNull(wakeLock); + } + + @Override + public void sendResult(Bundle data) { + final long identity = Binder.clearCallingIdentity(); + try { + mWakeLock.release(); + } catch (RuntimeException e) { + Log.e(TAG, "wakelock over-released by " + mIdentity, e); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } } diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java index 22a675ad39ab..5e38bca78a7c 100644 --- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java @@ -23,6 +23,8 @@ import static com.android.server.location.LocationManagerService.D; import static com.android.server.location.LocationManagerService.TAG; import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG; +import static java.lang.Math.max; + import android.annotation.Nullable; import android.location.Location; import android.location.LocationResult; @@ -53,6 +55,7 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation implements DeviceIdleHelper.DeviceIdleListener, DeviceIdleInternal.StationaryListener { private static final long MAX_STATIONARY_LOCATION_AGE_MS = 30000; + private static final long MIN_INTERVAL_MS = 1000; final Object mLock = new Object(); @@ -179,7 +182,7 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation && mLastLocation != null && mLastLocation.getElapsedRealtimeAgeMillis(mDeviceStationaryRealtimeMs) <= MAX_STATIONARY_LOCATION_AGE_MS) { - throttlingIntervalMs = mIncomingRequest.getIntervalMillis(); + throttlingIntervalMs = max(mIncomingRequest.getIntervalMillis(), MIN_INTERVAL_MS); } ProviderRequest newRequest; diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index a57d7db0ec54..1405dc45abbd 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -100,6 +100,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub // State guarded by mLock. private final Object mLock = new Object(); + private final SparseArray<UserRecord> mUserRecords = new SparseArray<>(); private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<>(); private int mCurrentUserId = -1; @@ -338,6 +339,23 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override + public void setBluetoothA2dpOn(IMediaRouterClient client, boolean on) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + mAudioService.setBluetoothA2dpOn(on); + } catch (RemoteException ex) { + Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn. on=" + on); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + @Override public void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan) { if (client == null) { @@ -903,8 +921,26 @@ public final class MediaRouterService extends IMediaRouterService.Stub if (intent.getAction().equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) { BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); synchronized (mLock) { + boolean wasA2dpOn = mGlobalBluetoothA2dpOn; mActiveBluetoothDevice = btDevice; mGlobalBluetoothA2dpOn = btDevice != null; + if (wasA2dpOn != mGlobalBluetoothA2dpOn) { + Slog.d(TAG, "GlobalBluetoothA2dpOn is changed to '" + + mGlobalBluetoothA2dpOn + "'"); + UserRecord userRecord = mUserRecords.get(mCurrentUserId); + if (userRecord != null) { + for (ClientRecord cr : userRecord.mClientRecords) { + // mSelectedRouteId will be null for BT and phone speaker. + if (cr.mSelectedRouteId == null) { + try { + cr.mClient.onGlobalA2dpChanged(mGlobalBluetoothA2dpOn); + } catch (RemoteException e) { + // Ignore exception + } + } + } + } + } } } } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index b477ea353c25..29a5469367cd 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -2128,6 +2128,23 @@ public class MediaSessionService extends SystemService implements Monitor { // Enabled notification listener only works within the same user. return false; } + // Verify whether package name and controller UID. + // It will indirectly check whether the caller has obtained the package name and UID + // via ControllerInfo or with the valid package name visibility. + try { + int actualControllerUid = mContext.getPackageManager().getPackageUidAsUser( + controllerPackageName, + UserHandle.getUserId(controllerUid)); + if (controllerUid != actualControllerUid) { + Log.w(TAG, "Failed to check enabled notification listener. Package name and" + + " UID doesn't match"); + return false; + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Failed to check enabled notification listener. Package name doesn't" + + " exist"); + return false; + } if (mNotificationManager.hasEnabledNotificationListener(controllerPackageName, UserHandle.getUserHandleForUid(controllerUid))) { diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java index 2519bbf389ba..8e7c4ff3e11c 100644 --- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java +++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java @@ -18,6 +18,7 @@ package com.android.server.media.metrics; import android.content.Context; import android.content.pm.PackageManager; +import android.media.MediaMetrics; import android.media.metrics.IMediaMetricsManager; import android.media.metrics.NetworkEvent; import android.media.metrics.PlaybackErrorEvent; @@ -65,6 +66,8 @@ public final class MediaMetricsManagerService extends SystemService { private static final int LOGGING_LEVEL_NO_UID = 1000; private static final int LOGGING_LEVEL_BLOCKED = 99999; + private static final String mMetricsId = MediaMetrics.Name.METRICS_MANAGER; + private static final String FAILED_TO_GET = "failed_to_get"; private final SecureRandom mSecureRandom; @GuardedBy("mLock") @@ -199,6 +202,12 @@ public final class MediaMetricsManagerService extends SystemService { mSecureRandom.nextBytes(byteId); String id = Base64.encodeToString( byteId, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE); + + // Authorize these session ids in the native mediametrics service. + new MediaMetrics.Item(mMetricsId) + .set(MediaMetrics.Property.EVENT, "create") + .set(MediaMetrics.Property.LOG_SESSION_ID, id) + .record(); return id; } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 84be7f5809e6..cfefffcdd2e8 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -4073,7 +4073,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (hasRestrictedModeAccess(uid)) { uidBlockedState.allowedReasons |= ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS; } else { - uidBlockedState.allowedReasons &= ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS; + uidBlockedState.allowedReasons &= ~ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS; } uidBlockedState.updateEffectiveBlockedReasons(); if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) { @@ -4899,6 +4899,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { ? ALLOWED_REASON_POWER_SAVE_ALLOWLIST : 0); newAllowedReasons |= (isWhitelistedFromPowerSaveExceptIdleUL(uid) ? ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST : 0); + newAllowedReasons |= (uidBlockedState.allowedReasons + & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS); if (LOGV) { Log.v(TAG, "updateRulesForPowerRestrictionsUL(" + uid + ")" diff --git a/services/core/java/com/android/server/notification/BadgeExtractor.java b/services/core/java/com/android/server/notification/BadgeExtractor.java index 70edfa171356..642cbb324229 100644 --- a/services/core/java/com/android/server/notification/BadgeExtractor.java +++ b/services/core/java/com/android/server/notification/BadgeExtractor.java @@ -69,11 +69,8 @@ public class BadgeExtractor implements NotificationSignalExtractor { if (mConfig.isMediaNotificationFilteringEnabled()) { final Notification notif = record.getNotification(); - if (notif.hasMediaSession()) { - if (notif.isStyle(Notification.DecoratedMediaCustomViewStyle.class) - || notif.isStyle(Notification.MediaStyle.class)) { - record.setShowBadge(false); - } + if (notif.isMediaNotification()) { + record.setShowBadge(false); } } return null; diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index ddaaa1eeff4a..7d31287663d5 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -114,9 +114,10 @@ abstract public class ManagedServices { static final String ATT_VERSION = "version"; static final String ATT_DEFAULTS = "defaults"; static final String ATT_USER_SET = "user_set_services"; + static final String ATT_USER_SET_OLD = "user_set"; static final String ATT_USER_CHANGED = "user_changed"; - static final int DB_VERSION = 4; + static final String DB_VERSION = "4"; static final int APPROVAL_BY_PACKAGE = 0; static final int APPROVAL_BY_COMPONENT = 1; @@ -482,7 +483,7 @@ abstract public class ManagedServices { public void writeXml(TypedXmlSerializer out, boolean forBackup, int userId) throws IOException { out.startTag(null, getConfig().xmlTag); - out.attributeInt(null, ATT_VERSION, DB_VERSION); + out.attributeInt(null, ATT_VERSION, Integer.parseInt(DB_VERSION)); writeDefaults(out); @@ -615,6 +616,7 @@ abstract public class ManagedServices { // read grants int type; String version = XmlUtils.readStringAttribute(parser, ATT_VERSION); + boolean needUpgradeUserset = false; readDefaults(parser); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { String tag = parser.getName(); @@ -633,13 +635,42 @@ abstract public class ManagedServices { final boolean isPrimary = parser.getAttributeBoolean(null, ATT_IS_PRIMARY, true); + // Load three different userSet attributes from xml + // user_changed, not null if version == 4 and is NAS setting final String isUserChanged = XmlUtils.readStringAttribute(parser, ATT_USER_CHANGED); - String userSetComponent = null; - if (isUserChanged == null) { - userSetComponent = XmlUtils.readStringAttribute(parser, ATT_USER_SET); + // user_set, not null if version <= 3 + final String isUserChanged_Old = XmlUtils.readStringAttribute(parser, + ATT_USER_SET_OLD); + // user_set_services, not null if version >= 3 and is non-NAS setting + String userSetComponent = XmlUtils.readStringAttribute(parser, ATT_USER_SET); + + // since the same xml version may have different userSet attributes, + // we need to check both xml version and userSet values to know how to set + // the userSetComponent/mIsUserChanged to the correct value + if (DB_VERSION.equals(version)) { + // version 4, NAS contains user_changed and + // NLS/others contain user_set_services + if (isUserChanged == null) { //NLS + userSetComponent = TextUtils.emptyIfNull(userSetComponent); + } else { //NAS + mIsUserChanged.put(resolvedUserId, Boolean.valueOf(isUserChanged)); + userSetComponent = Boolean.valueOf(isUserChanged) ? approved : ""; + } } else { - mIsUserChanged.put(resolvedUserId, Boolean.valueOf(isUserChanged)); + // version 3 may contain user_set (R) or user_set_services (S) + // version 2 or older contain user_set or nothing + needUpgradeUserset = true; + if (userSetComponent == null) { //contains user_set + if (isUserChanged_Old != null && Boolean.valueOf(isUserChanged_Old)) { + //user_set = true + userSetComponent = approved; + mIsUserChanged.put(resolvedUserId, true); + needUpgradeUserset = false; + } else { + userSetComponent = ""; + } + } } readExtraAttributes(tag, parser, resolvedUserId); if (allowedManagedServicePackages == null || allowedManagedServicePackages.test( @@ -659,7 +690,6 @@ abstract public class ManagedServices { || DB_VERSION_1.equals(version) || DB_VERSION_2.equals(version) || DB_VERSION_3.equals(version); - boolean needUpgradeUserset = DB_VERSION_3.equals(version); if (isOldVersion) { upgradeDefaultsXmlVersion(); } diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java index 8aae6e09bd31..583cdd599780 100644 --- a/services/core/java/com/android/server/notification/NotificationComparator.java +++ b/services/core/java/com/android/server/notification/NotificationComparator.java @@ -179,7 +179,7 @@ public class NotificationComparator } private boolean isMediaNotification(NotificationRecord record) { - return record.getNotification().hasMediaSession(); + return record.getNotification().isMediaNotification(); } private boolean isCallCategory(NotificationRecord record) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 7ba0f04a435f..211f8d6e3ec7 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -149,6 +149,7 @@ import android.app.NotificationHistory.HistoricalNotification; import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.app.PendingIntent; +import android.app.RemoteServiceException.BadForegroundServiceNotificationException; import android.app.StatsManager; import android.app.StatusBarManager; import android.app.UriGrantsManager; @@ -265,7 +266,6 @@ import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; @@ -614,12 +614,6 @@ public class NotificationManagerService extends SystemService { private NotificationRecordLogger mNotificationRecordLogger; private InstanceIdSequence mNotificationInstanceIdSequence; private Set<String> mMsgPkgsAllowedAsConvos = new HashSet(); - protected static final String ACTION_ENABLE_NAS = - "android.server.notification.action.ENABLE_NAS"; - protected static final String ACTION_DISABLE_NAS = - "android.server.notification.action.DISABLE_NAS"; - protected static final String ACTION_LEARNMORE_NAS = - "android.server.notification.action.LEARNMORE_NAS"; static class Archive { final SparseArray<Boolean> mEnabled; @@ -754,95 +748,25 @@ public class NotificationManagerService extends SystemService { setDefaultAssistantForUser(userId); } - protected void migrateDefaultNASShowNotificationIfNecessary() { + protected void migrateDefaultNAS() { final List<UserInfo> activeUsers = mUm.getUsers(); for (UserInfo userInfo : activeUsers) { int userId = userInfo.getUserHandle().getIdentifier(); if (isNASMigrationDone(userId) || mUm.isManagedProfile(userId)) { continue; } - if (mAssistants.hasUserSet(userId)) { - ComponentName defaultFromConfig = mAssistants.getDefaultFromConfig(); - List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId); - if (allowedComponents.size() == 0) { - setNASMigrationDone(userId); - mAssistants.clearDefaults(); - continue; - } else if (allowedComponents.contains(defaultFromConfig)) { - setNASMigrationDone(userId); - mAssistants.resetDefaultFromConfig(); - continue; - } - // TODO(b/192450820): re-enable when "user set" isn't over triggering - //User selected different NAS, need onboarding - /*enqueueNotificationInternal(getContext().getPackageName(), - getContext().getOpPackageName(), Binder.getCallingUid(), - Binder.getCallingPid(), TAG, - SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, - createNASUpgradeNotification(userId), userId);*/ - } - } - } - - protected Notification createNASUpgradeNotification(int userId) { - final Bundle extras = new Bundle(); - extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, - getContext().getResources().getString(R.string.global_action_settings)); - int title = R.string.nas_upgrade_notification_title; - int content = R.string.nas_upgrade_notification_content; - - Intent onboardingIntent = new Intent(Settings.ACTION_NOTIFICATION_ASSISTANT_SETTINGS); - onboardingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - - Intent enableIntent = new Intent(ACTION_ENABLE_NAS); - enableIntent.putExtra(Intent.EXTRA_USER_ID, userId); - PendingIntent enableNASPendingIntent = PendingIntent.getBroadcast(getContext(), - 0, enableIntent, PendingIntent.FLAG_IMMUTABLE); - - Intent disableIntent = new Intent(ACTION_DISABLE_NAS); - disableIntent.putExtra(Intent.EXTRA_USER_ID, userId); - PendingIntent disableNASPendingIntent = PendingIntent.getBroadcast(getContext(), - 0, disableIntent, PendingIntent.FLAG_IMMUTABLE); - - Intent learnMoreIntent = new Intent(ACTION_LEARNMORE_NAS); - learnMoreIntent.putExtra(Intent.EXTRA_USER_ID, userId); - PendingIntent learnNASPendingIntent = PendingIntent.getBroadcast(getContext(), - 0, learnMoreIntent, PendingIntent.FLAG_IMMUTABLE); - - Notification.Action enableNASAction = new Notification.Action.Builder( - 0, - getContext().getResources().getString( - R.string.nas_upgrade_notification_enable_action), - enableNASPendingIntent).build(); - - Notification.Action disableNASAction = new Notification.Action.Builder( - 0, - getContext().getResources().getString( - R.string.nas_upgrade_notification_disable_action), - disableNASPendingIntent).build(); - - Notification.Action learnMoreNASAction = new Notification.Action.Builder( - 0, - getContext().getResources().getString( - R.string.nas_upgrade_notification_learn_more_action), - learnNASPendingIntent).build(); - - - return new Notification.Builder(getContext(), SystemNotificationChannels.SYSTEM_CHANGES) - .setAutoCancel(false) - .setOngoing(true) - .setTicker(getContext().getResources().getString(title)) - .setSmallIcon(R.drawable.ic_settings_24dp) - .setContentTitle(getContext().getResources().getString(title)) - .setContentText(getContext().getResources().getString(content)) - .setContentIntent(PendingIntent.getActivity(getContext(), 0, onboardingIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE)) - .setLocalOnly(true) - .setStyle(new Notification.BigTextStyle()) - .addAction(enableNASAction) - .addAction(disableNASAction) - .addAction(learnMoreNASAction) - .build(); + List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId); + if (allowedComponents.size() == 0) { // user set to none + Slog.d(TAG, "NAS Migration: user set to none, disable new NAS setting"); + setNASMigrationDone(userId); + mAssistants.clearDefaults(); + } else { + Slog.d(TAG, "Reset NAS setting and migrate to new default"); + resetAssistantUserSet(userId); + // migrate to new default and set migration done + mAssistants.resetDefaultAssistantsIfNecessary(); + } + } } @VisibleForTesting @@ -1256,10 +1180,11 @@ public class NotificationManagerService extends SystemService { // Still crash for foreground services, preventing the not-crash behaviour abused // by apps to give us a garbage notification and silently start a fg service. Binder.withCleanCallingIdentity( - () -> mAm.crashApplication(uid, initialPid, pkg, -1, + () -> mAm.crashApplicationWithType(uid, initialPid, pkg, -1, "Bad notification(tag=" + tag + ", id=" + id + ") posted from package " + pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): " - + message, true /* force */)); + + message, true /* force */, + BadForegroundServiceNotificationException.TYPE_ID)); } } @@ -1859,41 +1784,6 @@ public class NotificationManagerService extends SystemService { } }; - private final BroadcastReceiver mNASIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1); - if (ACTION_ENABLE_NAS.equals(action)) { - mAssistants.resetDefaultFromConfig(); - setNotificationAssistantAccessGrantedForUserInternal( - CollectionUtils.firstOrNull(mAssistants.getDefaultComponents()), - userId, true, true); - setNASMigrationDone(userId); - cancelNotificationInternal(getContext().getPackageName(), - getContext().getOpPackageName(), Binder.getCallingUid(), - Binder.getCallingPid(), TAG, - SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId); - } else if (ACTION_DISABLE_NAS.equals(action)) { - //Set default NAS to be null if user selected none during migration - mAssistants.clearDefaults(); - setNotificationAssistantAccessGrantedForUserInternal( - null, userId, true, true); - setNASMigrationDone(userId); - cancelNotificationInternal(getContext().getPackageName(), - getContext().getOpPackageName(), Binder.getCallingUid(), - Binder.getCallingPid(), TAG, - SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId); - } else if (ACTION_LEARNMORE_NAS.equals(action)) { - Intent i = new Intent(getContext(), NASLearnMoreActivity.class); - i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - getContext().sendBroadcastAsUser( - new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.of(userId)); - getContext().startActivity(i); - } - } - }; - private final class SettingsObserver extends ContentObserver { private final Uri NOTIFICATION_BADGING_URI = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING); @@ -2405,12 +2295,6 @@ public class NotificationManagerService extends SystemService { IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED); getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter); - - IntentFilter nasFilter = new IntentFilter(); - nasFilter.addAction(ACTION_ENABLE_NAS); - nasFilter.addAction(ACTION_DISABLE_NAS); - nasFilter.addAction(ACTION_LEARNMORE_NAS); - getContext().registerReceiver(mNASIntentReceiver, nasFilter); } /** @@ -2422,7 +2306,6 @@ public class NotificationManagerService extends SystemService { getContext().unregisterReceiver(mNotificationTimeoutReceiver); getContext().unregisterReceiver(mRestoreReceiver); getContext().unregisterReceiver(mLocaleChangeReceiver); - getContext().unregisterReceiver(mNASIntentReceiver); if (mDeviceConfigChangedListener != null) { DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener); @@ -2689,7 +2572,7 @@ public class NotificationManagerService extends SystemService { mConditionProviders.onBootPhaseAppsCanStart(); mHistoryManager.onBootPhaseAppsCanStart(); registerDeviceConfigChange(); - migrateDefaultNASShowNotificationIfNecessary(); + migrateDefaultNAS(); } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis()); } @@ -2803,7 +2686,7 @@ public class NotificationManagerService extends SystemService { } } - private void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, + void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromApp, boolean fromListener) { Objects.requireNonNull(group); Objects.requireNonNull(pkg); @@ -3851,7 +3734,8 @@ public class NotificationManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); NotificationChannelGroup groupToDelete = - mPreferencesHelper.getNotificationChannelGroup(groupId, pkg, callingUid); + mPreferencesHelper.getNotificationChannelGroupWithChannels( + pkg, callingUid, groupId, false); if (groupToDelete != null) { // Preflight for allowability final int userId = UserHandle.getUserId(callingUid); @@ -5227,10 +5111,6 @@ public class NotificationManagerService extends SystemService { public void setNASMigrationDoneAndResetDefault(int userId, boolean loadFromConfig) { checkCallerIsSystem(); setNASMigrationDone(userId); - cancelNotificationInternal(getContext().getPackageName(), - getContext().getOpPackageName(), Binder.getCallingUid(), - Binder.getCallingPid(), TAG, - SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId); if (loadFromConfig) { mAssistants.resetDefaultFromConfig(); } else { @@ -7065,7 +6945,9 @@ public class NotificationManagerService extends SystemService { if (index < 0) { mNotificationList.add(r); mUsageStats.registerPostedByApp(r); - r.setInterruptive(isVisuallyInterruptive(null, r)); + final boolean isInterruptive = isVisuallyInterruptive(null, r); + r.setInterruptive(isInterruptive); + r.setTextChanged(isInterruptive); } else { old = mNotificationList.get(index); // Potentially *changes* old mNotificationList.set(index, r); @@ -7076,7 +6958,6 @@ public class NotificationManagerService extends SystemService { r.isUpdate = true; final boolean isInterruptive = isVisuallyInterruptive(old, r); r.setTextChanged(isInterruptive); - r.setInterruptive(isInterruptive); } mNotificationsByKey.put(n.getKey(), r); @@ -7700,7 +7581,10 @@ public class NotificationManagerService extends SystemService { final int waitMs = mAudioManager.getFocusRampTimeMs( AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, record.getAudioAttributes()); - if (DBG) Slog.v(TAG, "Delaying vibration by " + waitMs + "ms"); + if (DBG) { + Slog.v(TAG, "Delaying vibration for notification " + + record.getKey() + " by " + waitMs + "ms"); + } try { Thread.sleep(waitMs); } catch (InterruptedException e) { } @@ -7708,9 +7592,17 @@ public class NotificationManagerService extends SystemService { // so need to check the notification still valide for vibrate. synchronized (mNotificationLock) { if (mNotificationsByKey.get(record.getKey()) != null) { - vibrate(record, effect, true); + if (record.getKey().equals(mVibrateNotificationKey)) { + vibrate(record, effect, true); + } else { + if (DBG) { + Slog.v(TAG, "No vibration for notification " + + record.getKey() + ": a new notification is " + + "vibrating, or effects were cleared while waiting"); + } + } } else { - Slog.e(TAG, "No vibration for canceled notification : " + Slog.w(TAG, "No vibration for canceled notification " + record.getKey()); } } @@ -9424,7 +9316,7 @@ public class NotificationManagerService extends SystemService { record.getSystemGeneratedSmartActions(), record.getSmartReplies(), record.canBubble(), - record.isInterruptive(), + record.isTextChanged(), record.isConversation(), record.getShortcutInfo(), record.getRankingScore() == 0 diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index b4ca5118e10f..b6b54fc19011 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -1143,6 +1143,10 @@ public final class NotificationRecord { return mIsInterruptive; } + public boolean isTextChanged() { + return mTextChanged; + } + /** Returns the time the notification audibly alerted the user. */ public long getLastAudiblyAlertedMs() { return mLastAudiblyAlertedMs; diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index e8a3a8150377..185c0231b534 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -796,6 +796,9 @@ public class PreferencesHelper implements RankingConfig { if (r == null) { throw new IllegalArgumentException("Invalid package"); } + if (fromTargetApp) { + group.setBlocked(false); + } final NotificationChannelGroup oldGroup = r.groups.get(group.getId()); if (oldGroup != null) { group.setChannels(oldGroup.getChannels()); diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java index f47aa487744a..449fae13f137 100644 --- a/services/core/java/com/android/server/notification/VibratorHelper.java +++ b/services/core/java/com/android/server/notification/VibratorHelper.java @@ -19,6 +19,7 @@ package com.android.server.notification; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; +import android.content.res.TypedArray; import android.media.AudioAttributes; import android.os.Process; import android.os.VibrationAttributes; @@ -43,11 +44,12 @@ public final class VibratorHelper { private final Vibrator mVibrator; private final long[] mDefaultPattern; private final long[] mFallbackPattern; + @Nullable private final float[] mDefaultPwlePattern; + @Nullable private final float[] mFallbackPwlePattern; public VibratorHelper(Context context) { mVibrator = context.getSystemService(Vibrator.class); - mDefaultPattern = getLongArray( - context.getResources(), + mDefaultPattern = getLongArray(context.getResources(), com.android.internal.R.array.config_defaultNotificationVibePattern, VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); @@ -55,6 +57,10 @@ public final class VibratorHelper { R.array.config_notificationFallbackVibePattern, VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); + mDefaultPwlePattern = getFloatArray(context.getResources(), + com.android.internal.R.array.config_defaultNotificationVibeWaveform); + mFallbackPwlePattern = getFloatArray(context.getResources(), + com.android.internal.R.array.config_notificationFallbackVibeWaveform); } /** @@ -80,6 +86,50 @@ public final class VibratorHelper { } /** + * Safely create a {@link VibrationEffect} from given waveform description. + * + * <p>The waveform is described by a sequence of values for target amplitude, frequency and + * duration, that are forwarded to + * {@link VibrationEffect.WaveformBuilder#addRamp(float, float, int)}. + * + * <p>This method returns {@code null} if the pattern is also {@code null} or invalid. + * + * @param values The list of values describing the waveform as a sequence of target amplitude, + * frequency and duration. + * @param insistent {@code true} if the vibration should loop until it is cancelled. + */ + @Nullable + public static VibrationEffect createPwleWaveformVibration(@Nullable float[] values, + boolean insistent) { + try { + if (values == null) { + return null; + } + + int length = values.length; + // The waveform is described by triples (amplitude, frequency, duration) + if ((length == 0) || (length % 3 != 0)) { + return null; + } + + VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform(); + for (int i = 0; i < length; i += 3) { + waveformBuilder.addRamp(/* amplitude= */ values[i], /* frequency= */ values[i + 1], + /* duration= */ (int) values[i + 2]); + } + + if (insistent) { + return waveformBuilder.build(/* repeat= */ 0); + } + return waveformBuilder.build(); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Error creating vibration PWLE waveform with pattern: " + + Arrays.toString(values)); + } + return null; + } + + /** * Vibrate the device with given {@code effect}. * * <p>We need to vibrate as "android" so we can breakthrough DND. @@ -102,6 +152,12 @@ public final class VibratorHelper { * @param insistent {@code true} if the vibration should loop until it is cancelled. */ public VibrationEffect createFallbackVibration(boolean insistent) { + if (mVibrator.hasFrequencyControl()) { + VibrationEffect effect = createPwleWaveformVibration(mFallbackPwlePattern, insistent); + if (effect != null) { + return effect; + } + } return createWaveformVibration(mFallbackPattern, insistent); } @@ -111,9 +167,32 @@ public final class VibratorHelper { * @param insistent {@code true} if the vibration should loop until it is cancelled. */ public VibrationEffect createDefaultVibration(boolean insistent) { + if (mVibrator.hasFrequencyControl()) { + VibrationEffect effect = createPwleWaveformVibration(mDefaultPwlePattern, insistent); + if (effect != null) { + return effect; + } + } return createWaveformVibration(mDefaultPattern, insistent); } + @Nullable + private static float[] getFloatArray(Resources resources, int resId) { + TypedArray array = resources.obtainTypedArray(resId); + try { + float[] values = new float[array.length()]; + for (int i = 0; i < values.length; i++) { + values[i] = array.getFloat(i, Float.NaN); + if (Float.isNaN(values[i])) { + return null; + } + } + return values; + } finally { + array.recycle(); + } + } + private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) { int[] ar = resources.getIntArray(resId); if (ar == null) { diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java index ed1f5f567d95..3fc416931a06 100644 --- a/services/core/java/com/android/server/os/NativeTombstoneManager.java +++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java @@ -356,7 +356,7 @@ public final class NativeTombstoneManager { return false; } - if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 1000) { + if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 5000) { return false; } diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index d6400f3c879e..b500c9930e00 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -381,7 +381,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { } // Does the package have code? If not, there won't be any artifacts. - if (!PackageDexOptimizer.canOptimizePackage(pkg)) { + if (!mPackageManagerService.mPackageDexOptimizer.canOptimizePackage(pkg)) { continue; } if (pkg.getPath() == null) { diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 44f7d8869322..040457bf9000 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -63,7 +63,10 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.LocalServices; +import com.android.server.apphibernation.AppHibernationManagerInternal; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.dex.ArtStatsLogUtils; @@ -107,16 +110,24 @@ public class PackageDexOptimizer { private volatile boolean mSystemReady; private final ArtStatsLogger mArtStatsLogger = new ArtStatsLogger(); + private final Injector mInjector; + private static final Random sRandom = new Random(); PackageDexOptimizer(Installer installer, Object installLock, Context context, String wakeLockTag) { - this.mInstaller = installer; - this.mInstallLock = installLock; + this(new Injector() { + @Override + public AppHibernationManagerInternal getAppHibernationManagerInternal() { + return LocalServices.getService(AppHibernationManagerInternal.class); + } - PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); - mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag); + @Override + public PowerManager getPowerManager(Context context) { + return context.getSystemService(PowerManager.class); + } + }, installer, installLock, context, wakeLockTag); } protected PackageDexOptimizer(PackageDexOptimizer from) { @@ -124,9 +135,21 @@ public class PackageDexOptimizer { this.mInstallLock = from.mInstallLock; this.mDexoptWakeLock = from.mDexoptWakeLock; this.mSystemReady = from.mSystemReady; + this.mInjector = from.mInjector; + } + + @VisibleForTesting + PackageDexOptimizer(@NonNull Injector injector, Installer installer, Object installLock, + Context context, String wakeLockTag) { + this.mInstaller = installer; + this.mInstallLock = installLock; + + PowerManager powerManager = injector.getPowerManager(context); + mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag); + mInjector = injector; } - static boolean canOptimizePackage(AndroidPackage pkg) { + boolean canOptimizePackage(AndroidPackage pkg) { // We do not dexopt a package with no code. // Note that the system package is marked as having no code, however we can // still optimize it via dexoptSystemServerPath. @@ -134,6 +157,17 @@ public class PackageDexOptimizer { return false; } + // We do not dexopt unused packages. + // It's possible for this to be called before app hibernation service is ready due to + // an OTA dexopt. In this case, we ignore the hibernation check here. This is fine since + // a hibernating app should have no artifacts to copy in the first place. + AppHibernationManagerInternal ahm = mInjector.getAppHibernationManagerInternal(); + if (ahm != null + && ahm.isHibernatingGlobally(pkg.getPackageName()) + && ahm.isOatArtifactDeletionEnabled()) { + return false; + } + return true; } @@ -921,4 +955,13 @@ public class PackageDexOptimizer { return flags | DEXOPT_FORCE; } } + + /** + * Injector for {@link PackageDexOptimizer} dependencies + */ + interface Injector { + AppHibernationManagerInternal getAppHibernationManagerInternal(); + + PowerManager getPowerManager(Context context); + } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index f317d311c87a..d07907671703 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -388,7 +388,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements if (age >= MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS) { // Aggressively close old sessions because we are running low on storage // Their staging dirs will be removed too - session.abandon(); + PackageInstallerSession root = !session.hasParentSessionId() + ? session : mSessions.get(session.getParentSessionId()); + if (!root.isDestroyed()) { + root.abandon(); + } } else { // Session is new enough, so it deserves to be kept even on low storage unclaimedStagingDirsOnVolume.remove(session.stageDir); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 1d14bb59c56a..70ecaa7a458c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -368,7 +368,6 @@ import com.android.server.SystemConfig; import com.android.server.SystemServerInitThreadPool; import com.android.server.Watchdog; import com.android.server.apphibernation.AppHibernationManagerInternal; -import com.android.server.apphibernation.AppHibernationService; import com.android.server.compat.CompatChange; import com.android.server.compat.PlatformCompat; import com.android.server.net.NetworkPolicyManagerInternal; @@ -1471,7 +1470,7 @@ public class PackageManagerService extends IPackageManager.Stub final ArtManagerService mArtManagerService; - private final PackageDexOptimizer mPackageDexOptimizer; + final PackageDexOptimizer mPackageDexOptimizer; // DexManager handles the usage of dex files (e.g. secondary files, whether or not a package // is used by other apps). private final DexManager mDexManager; @@ -4807,7 +4806,7 @@ public class PackageManagerService extends IPackageManager.Stub try { mDomainVerificationManager.printState(writer, packageName, UserHandle.USER_ALL, mSettings::getPackageLPr); - } catch (PackageManager.NameNotFoundException e) { + } catch (Exception e) { pw.println("Failure printing domain verification information"); Slog.e(TAG, "Failure printing domain verification information", e); } @@ -10695,7 +10694,7 @@ public class PackageManagerService extends IPackageManager.Stub userId); // Find any earlier preferred or last chosen entries and nuke them findPreferredActivityNotLocked( - intent, resolvedType, flags, query, false, true, false, userId); + intent, resolvedType, flags, query, 0, false, true, false, userId); // Add the new activity as the last chosen for this filter addPreferredActivity(filter, match, null, activity, false, userId, "Setting last chosen", false); @@ -10711,7 +10710,7 @@ public class PackageManagerService extends IPackageManager.Stub final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags, userId); return findPreferredActivityNotLocked( - intent, resolvedType, flags, query, false, false, false, userId); + intent, resolvedType, flags, query, 0, false, false, false, userId); } private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj, @@ -10753,7 +10752,7 @@ public class PackageManagerService extends IPackageManager.Stub // If we have saved a preference for a preferred activity for // this Intent, use that. ResolveInfo ri = findPreferredActivityNotLocked(intent, resolvedType, - flags, query, true, false, debug, userId, queryMayBeFiltered); + flags, query, r0.priority, true, false, debug, userId, queryMayBeFiltered); if (ri != null) { return ri; } @@ -10899,17 +10898,17 @@ public class PackageManagerService extends IPackageManager.Stub } ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags, - List<ResolveInfo> query, boolean always, + List<ResolveInfo> query, int priority, boolean always, boolean removeMatches, boolean debug, int userId) { return findPreferredActivityNotLocked( - intent, resolvedType, flags, query, always, removeMatches, debug, userId, + intent, resolvedType, flags, query, priority, always, removeMatches, debug, userId, UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID); } // TODO: handle preferred activities missing while user has amnesia /** <b>must not hold {@link #mLock}</b> */ ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags, - List<ResolveInfo> query, boolean always, + List<ResolveInfo> query, int priority, boolean always, boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) { if (Thread.holdsLock(mLock)) { Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() @@ -12866,7 +12865,7 @@ public class PackageManagerService extends IPackageManager.Stub } } - if (!PackageDexOptimizer.canOptimizePackage(pkg)) { + if (!mPackageDexOptimizer.canOptimizePackage(pkg)) { if (DEBUG_DEXOPT) { Log.i(TAG, "Skipping update of non-optimizable app " + pkg.getPackageName()); } @@ -12947,7 +12946,7 @@ public class PackageManagerService extends IPackageManager.Stub return; } } else { - if (isInstantApp(packageName, callingUserId)) { + if (isInstantAppInternal(packageName, callingUserId, Process.SYSTEM_UID)) { return; } } @@ -13117,16 +13116,11 @@ public class PackageManagerService extends IPackageManager.Stub ArraySet<String> pkgs = new ArraySet<>(); synchronized (mLock) { for (AndroidPackage p : mPackages.values()) { - if (PackageDexOptimizer.canOptimizePackage(p)) { + if (mPackageDexOptimizer.canOptimizePackage(p)) { pkgs.add(p.getPackageName()); } } } - if (AppHibernationService.isAppHibernationEnabled()) { - AppHibernationManagerInternal appHibernationManager = - mInjector.getLocalService(AppHibernationManagerInternal.class); - pkgs.removeIf(pkgName -> appHibernationManager.isHibernatingGlobally(pkgName)); - } return pkgs; } @@ -15486,7 +15480,8 @@ public class PackageManagerService extends IPackageManager.Stub mResolveActivity.processName = "system:ui"; mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER; - mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; + mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS + | ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert; mResolveActivity.exported = true; mResolveActivity.enabled = true; @@ -23776,7 +23771,7 @@ public class PackageManagerService extends IPackageManager.Stub final List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId); final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked( - intent, null, 0, resolveInfos, true, false, false, userId); + intent, null, 0, resolveInfos, 0, true, false, false, userId); final String packageName = preferredResolveInfo != null && preferredResolveInfo.activityInfo != null ? preferredResolveInfo.activityInfo.packageName : null; @@ -24477,24 +24472,24 @@ public class PackageManagerService extends IPackageManager.Stub } enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, true /* checkShell */, "stop package"); - boolean shouldUnhibernate = false; // writer synchronized (mLock) { final PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps != null && ps.getStopped(userId) && !stopped) { - shouldUnhibernate = true; - } if (!shouldFilterApplicationLocked(ps, callingUid, userId) && mSettings.setPackageStoppedStateLPw(this, packageName, stopped, userId)) { scheduleWritePackageRestrictionsLocked(userId); } } - if (shouldUnhibernate) { + // If this would cause the app to leave force-stop, then also make sure to unhibernate the + // app if needed. + if (!stopped) { mHandler.post(() -> { AppHibernationManagerInternal ah = mInjector.getLocalService(AppHibernationManagerInternal.class); - ah.setHibernatingForUser(packageName, userId, false); - ah.setHibernatingGlobally(packageName, false); + if (ah != null && ah.isHibernatingForUser(packageName, userId)) { + ah.setHibernatingForUser(packageName, userId, false); + ah.setHibernatingGlobally(packageName, false); + } }); } } @@ -29007,8 +29002,12 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void setSplashScreenTheme(@NonNull String packageName, @Nullable String themeId, int userId) { - int callingUid = Binder.getCallingUid(); - PackageSetting packageSetting = getPackageSettingForUser(packageName, callingUid, userId); + final int callingUid = Binder.getCallingUid(); + enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */, + false /* checkShell */, "setSplashScreenTheme"); + enforceOwnerRights(packageName, callingUid); + final PackageSetting packageSetting = getPackageSettingForUser(packageName, callingUid, + userId); if (packageSetting != null) { packageSetting.setSplashScreenTheme(userId, themeId); } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index c2c5d91eafa8..cb28637e8c10 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -914,6 +914,11 @@ final class DefaultPermissionGrantPolicy { } grantPermissionsToSystemPackage(pm, dialerPackage, userId, CONTACTS_PERMISSIONS, SMS_PERMISSIONS, MICROPHONE_PERMISSIONS, CAMERA_PERMISSIONS); + boolean isAndroidAutomotive = + mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0); + if (isAndroidAutomotive) { + grantPermissionsToSystemPackage(pm, dialerPackage, userId, NEARBY_DEVICES_PERMISSIONS); + } } private void grantDefaultPermissionsToDefaultSystemSmsApp(PackageManagerWrapper pm, diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java index 0b48b5c6dd70..2208e3c02c4d 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java @@ -70,8 +70,11 @@ public class DomainVerificationEnforcer { break; default: if (!proxy.isCallerVerifier(callingUid)) { - throw new SecurityException( - "Caller is not allowed to query domain verification state"); + mContext.enforcePermission(android.Manifest.permission.DUMP, + Binder.getCallingPid(), callingUid, + "Caller " + callingUid + + " is not allowed to query domain verification state"); + break; } mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES, diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index ba64d25178e7..2270df375245 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -896,7 +896,7 @@ public class DomainVerificationService extends SystemService oldPkgState.getUserStates(); int oldUserStatesSize = oldUserStates.size(); if (oldUserStatesSize > 0) { - ArraySet<String> newWebDomains = mCollector.collectValidAutoVerifyDomains(newPkg); + ArraySet<String> newWebDomains = mCollector.collectAllWebDomains(newPkg); for (int oldUserStatesIndex = 0; oldUserStatesIndex < oldUserStatesSize; oldUserStatesIndex++) { int userId = oldUserStates.keyAt(oldUserStatesIndex); @@ -1193,6 +1193,7 @@ public class DomainVerificationService extends SystemService @Nullable @UserIdInt Integer userId, @NonNull Function<String, PackageSetting> pkgSettingFunction) throws NameNotFoundException { + mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy); synchronized (mLock) { mDebug.printState(writer, packageName, userId, pkgSettingFunction, mAttachedPkgStates); } diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java index edd5f5f415c6..27a16e9bfdda 100644 --- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java +++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java @@ -41,6 +41,7 @@ import com.android.server.devicestate.DeviceState; import com.android.server.devicestate.DeviceStateProvider; import com.android.server.policy.devicestate.config.Conditions; import com.android.server.policy.devicestate.config.DeviceStateConfig; +import com.android.server.policy.devicestate.config.Flags; import com.android.server.policy.devicestate.config.LidSwitchCondition; import com.android.server.policy.devicestate.config.NumericRange; import com.android.server.policy.devicestate.config.SensorCondition; @@ -81,13 +82,14 @@ import javax.xml.datatype.DatatypeConfigurationException; public final class DeviceStateProviderImpl implements DeviceStateProvider, InputManagerInternal.LidSwitchCallback, SensorEventListener { private static final String TAG = "DeviceStateProviderImpl"; + private static final boolean DEBUG = false; private static final BooleanSupplier TRUE_BOOLEAN_SUPPLIER = () -> true; private static final BooleanSupplier FALSE_BOOLEAN_SUPPLIER = () -> false; @VisibleForTesting static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(MINIMUM_DEVICE_STATE, - "DEFAULT"); + "DEFAULT", 0 /* flags */); private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/"; private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/"; @@ -131,7 +133,26 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, config.getDeviceState()) { final int state = stateConfig.getIdentifier().intValue(); final String name = stateConfig.getName() == null ? "" : stateConfig.getName(); - deviceStateList.add(new DeviceState(state, name)); + + int flags = 0; + final Flags configFlags = stateConfig.getFlags(); + if (configFlags != null) { + List<String> configFlagStrings = configFlags.getFlag(); + for (int i = 0; i < configFlagStrings.size(); i++) { + final String configFlagString = configFlagStrings.get(i); + switch (configFlagString) { + case "FLAG_CANCEL_STICKY_REQUESTS": + flags |= DeviceState.FLAG_CANCEL_STICKY_REQUESTS; + break; + default: + Slog.w(TAG, "Parsed unknown flag with name: " + + configFlagString); + break; + } + } + } + + deviceStateList.add(new DeviceState(state, name, flags)); final Conditions condition = stateConfig.getConditions(); conditionsList.add(condition); @@ -193,6 +214,10 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, for (int i = 0; i < stateConditions.size(); i++) { final int state = deviceStates.get(i).getIdentifier(); + if (DEBUG) { + Slog.d(TAG, "Evaluating conditions for device state " + state + + " (" + deviceStates.get(i).getName() + ")"); + } final Conditions conditions = stateConditions.get(i); if (conditions == null) { mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER); @@ -213,6 +238,9 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, if (lidSwitchCondition != null) { suppliers.add(new LidSwitchBooleanSupplier(lidSwitchCondition.getOpen())); lidSwitchRequired = true; + if (DEBUG) { + Slog.d(TAG, "Lid switch required"); + } } List<SensorCondition> sensorConditions = conditions.getSensor(); @@ -229,6 +257,11 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, break; } + if (DEBUG) { + Slog.d(TAG, "Found sensor with type: " + expectedSensorType + + " (" + expectedSensorName + ")"); + } + suppliers.add(new SensorBooleanSupplier(foundSensor, sensorCondition.getValue())); sensorsRequired.add(foundSensor); } @@ -323,6 +356,10 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, int newState = mOrderedStates[0].getIdentifier(); for (int i = 0; i < mOrderedStates.length; i++) { int state = mOrderedStates[i].getIdentifier(); + if (DEBUG) { + Slog.d(TAG, "Checking conditions for " + mOrderedStates[i].getName() + "(" + + i + ")"); + } boolean conditionSatisfied; try { conditionSatisfied = mStateConditions.get(state).getAsBoolean(); @@ -330,10 +367,16 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, // Failed to compute the current state based on current available data. Return // with the expectation that notifyDeviceStateChangedIfNeeded() will be called // when a callback with the missing data is triggered. + if (DEBUG) { + Slog.d(TAG, "Unable to check current state", e); + } return; } if (conditionSatisfied) { + if (DEBUG) { + Slog.d(TAG, "Device State conditions satisfied, transition to " + state); + } newState = state; break; } @@ -355,6 +398,9 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, synchronized (mLock) { mIsLidOpen = lidOpen; } + if (DEBUG) { + Slog.d(TAG, "Lid switch state: " + (lidOpen ? "open" : "closed")); + } notifyDeviceStateChangedIfNeeded(); } @@ -440,6 +486,9 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, private boolean adheresToRange(float value, @NonNull NumericRange range) { final BigDecimal min = range.getMin_optional(); if (min != null) { + if (DEBUG) { + Slog.d(TAG, "value: " + value + ", constraint min: " + min.floatValue()); + } if (value <= min.floatValue()) { return false; } @@ -447,6 +496,10 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, final BigDecimal minInclusive = range.getMinInclusive_optional(); if (minInclusive != null) { + if (DEBUG) { + Slog.d(TAG, "value: " + value + ", constraint min-inclusive: " + + minInclusive.floatValue()); + } if (value < minInclusive.floatValue()) { return false; } @@ -454,6 +507,9 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, final BigDecimal max = range.getMax_optional(); if (max != null) { + if (DEBUG) { + Slog.d(TAG, "value: " + value + ", constraint max: " + max.floatValue()); + } if (value >= max.floatValue()) { return false; } @@ -461,6 +517,10 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, final BigDecimal maxInclusive = range.getMaxInclusive_optional(); if (maxInclusive != null) { + if (DEBUG) { + Slog.d(TAG, "value: " + value + ", constraint max-inclusive: " + + maxInclusive.floatValue()); + } if (value > maxInclusive.floatValue()) { return false; } diff --git a/services/core/java/com/android/server/policy/DisplayFoldController.java b/services/core/java/com/android/server/policy/DisplayFoldController.java index 3c9b1063ba93..04b5005aa283 100644 --- a/services/core/java/com/android/server/policy/DisplayFoldController.java +++ b/services/core/java/com/android/server/policy/DisplayFoldController.java @@ -16,10 +16,8 @@ package com.android.server.policy; -import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; -import android.hardware.ICameraService; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.FoldStateListener; import android.hardware.display.DisplayManagerInternal; @@ -27,13 +25,11 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.util.Slog; import android.view.DisplayInfo; import android.view.IDisplayFoldListener; import com.android.server.DisplayThread; import com.android.server.LocalServices; -import com.android.server.camera.CameraServiceProxy; import com.android.server.wm.WindowManagerInternal; /** @@ -41,13 +37,8 @@ import com.android.server.wm.WindowManagerInternal; * TODO(b/126160895): Move DisplayFoldController from PhoneWindowManager to DisplayPolicy. */ class DisplayFoldController { - private static final String TAG = "DisplayFoldController"; - private final WindowManagerInternal mWindowManagerInternal; private final DisplayManagerInternal mDisplayManagerInternal; - // Camera service proxy can be disabled through a config. - @Nullable - private final CameraServiceProxy mCameraServiceProxy; private final int mDisplayId; private final Handler mHandler; @@ -64,12 +55,10 @@ class DisplayFoldController { DisplayFoldController( Context context, WindowManagerInternal windowManagerInternal, - DisplayManagerInternal displayManagerInternal, - @Nullable CameraServiceProxy cameraServiceProxy, int displayId, Rect foldedArea, + DisplayManagerInternal displayManagerInternal, int displayId, Rect foldedArea, Handler handler) { mWindowManagerInternal = windowManagerInternal; mDisplayManagerInternal = displayManagerInternal; - mCameraServiceProxy = cameraServiceProxy; mDisplayId = displayId; mFoldedArea = new Rect(foldedArea); mHandler = handler; @@ -124,16 +113,6 @@ class DisplayFoldController { } } - if (mCameraServiceProxy != null) { - if (folded) { - mCameraServiceProxy.setDeviceStateFlags(ICameraService.DEVICE_STATE_FOLDED); - } else { - mCameraServiceProxy.clearDeviceStateFlags(ICameraService.DEVICE_STATE_FOLDED); - } - } else { - Slog.w(TAG, "Camera service unavailable to toggle folded state."); - } - mDurationLogger.setDeviceFolded(folded); mDurationLogger.logFocusedAppWithFoldState(folded, mFocusedApp); mFolded = folded; @@ -188,8 +167,6 @@ class DisplayFoldController { LocalServices.getService(WindowManagerInternal.class); final DisplayManagerInternal displayService = LocalServices.getService(DisplayManagerInternal.class); - final CameraServiceProxy cameraServiceProxy = - LocalServices.getService(CameraServiceProxy.class); final String configFoldedArea = context.getResources().getString( com.android.internal.R.string.config_foldedArea); @@ -201,6 +178,6 @@ class DisplayFoldController { } return new DisplayFoldController(context, windowManagerService, displayService, - cameraServiceProxy, displayId, foldedArea, DisplayThread.getHandler()); + displayId, foldedArea, DisplayThread.getHandler()); } } diff --git a/services/core/java/com/android/server/policy/KeyCombinationManager.java b/services/core/java/com/android/server/policy/KeyCombinationManager.java index 268de3e2182b..68e078c519ba 100644 --- a/services/core/java/com/android/server/policy/KeyCombinationManager.java +++ b/services/core/java/com/android/server/policy/KeyCombinationManager.java @@ -17,11 +17,13 @@ package com.android.server.policy; import static android.view.KeyEvent.KEYCODE_POWER; +import android.os.Handler; import android.os.SystemClock; import android.util.Log; import android.util.SparseLongArray; import android.view.KeyEvent; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ToBooleanFunction; import java.io.PrintWriter; @@ -35,13 +37,18 @@ public class KeyCombinationManager { private static final String TAG = "KeyCombinationManager"; // Store the received down time of keycode. + @GuardedBy("mLock") private final SparseLongArray mDownTimes = new SparseLongArray(2); private final ArrayList<TwoKeysCombinationRule> mRules = new ArrayList(); // Selected rules according to current key down. + private final Object mLock = new Object(); + @GuardedBy("mLock") private final ArrayList<TwoKeysCombinationRule> mActiveRules = new ArrayList(); // The rule has been triggered by current keys. + @GuardedBy("mLock") private TwoKeysCombinationRule mTriggeredRule; + private final Handler mHandler = new Handler(); // Keys in a key combination must be pressed within this interval of each other. private static final long COMBINE_KEY_DELAY_MILLIS = 150; @@ -109,6 +116,12 @@ public class KeyCombinationManager { * Return true if any active rule could be triggered by the key event, otherwise false. */ boolean interceptKey(KeyEvent event, boolean interactive) { + synchronized (mLock) { + return interceptKeyLocked(event, interactive); + } + } + + private boolean interceptKeyLocked(KeyEvent event, boolean interactive) { final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final int keyCode = event.getKeyCode(); final int count = mActiveRules.size(); @@ -154,7 +167,7 @@ public class KeyCombinationManager { return false; } Log.v(TAG, "Performing combination rule : " + rule); - rule.execute(); + mHandler.post(rule::execute); mTriggeredRule = rule; return true; }); @@ -169,7 +182,7 @@ public class KeyCombinationManager { for (int index = count - 1; index >= 0; index--) { final TwoKeysCombinationRule rule = mActiveRules.get(index); if (rule.shouldInterceptKey(keyCode)) { - rule.cancel(); + mHandler.post(rule::cancel); mActiveRules.remove(index); } } @@ -181,31 +194,37 @@ public class KeyCombinationManager { * Return the interceptTimeout to tell InputDispatcher when is ready to deliver to window. */ long getKeyInterceptTimeout(int keyCode) { - if (forAllActiveRules((rule) -> rule.shouldInterceptKey(keyCode))) { - return mDownTimes.get(keyCode) + COMBINE_KEY_DELAY_MILLIS; + synchronized (mLock) { + if (forAllActiveRules((rule) -> rule.shouldInterceptKey(keyCode))) { + return mDownTimes.get(keyCode) + COMBINE_KEY_DELAY_MILLIS; + } + return 0; } - return 0; } /** * True if the key event had been handled. */ boolean isKeyConsumed(KeyEvent event) { - if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) { - return false; + synchronized (mLock) { + if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) { + return false; + } + return mTriggeredRule != null && mTriggeredRule.shouldInterceptKey(event.getKeyCode()); } - return mTriggeredRule != null && mTriggeredRule.shouldInterceptKey(event.getKeyCode()); } /** * True if power key is the candidate. */ boolean isPowerKeyIntercepted() { - if (forAllActiveRules((rule) -> rule.shouldInterceptKey(KEYCODE_POWER))) { - // return false if only if power key pressed. - return mDownTimes.size() > 1 || mDownTimes.get(KEYCODE_POWER) == 0; + synchronized (mLock) { + if (forAllActiveRules((rule) -> rule.shouldInterceptKey(KEYCODE_POWER))) { + // return false if only if power key pressed. + return mDownTimes.size() > 1 || mDownTimes.get(KEYCODE_POWER) == 0; + } + return false; } - return false; } /** diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 138d55e09742..b02d08027510 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -47,7 +47,6 @@ import static android.view.KeyEvent.KEYCODE_VOLUME_UP; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; @@ -405,7 +404,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { private AccessibilityShortcutController mAccessibilityShortcutController; boolean mSafeMode; - private WindowState mKeyguardCandidate = null; // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key. // This is for car dock and this is updated from resource. @@ -508,7 +506,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { private boolean mKeyguardOccludedChanged; private ActivityTaskManagerInternal.SleepTokenAcquirer mScreenOffSleepTokenAcquirer; - volatile boolean mKeyguardOccluded; Intent mHomeIntent; Intent mCarDockIntent; Intent mDeskDockIntent; @@ -1831,14 +1828,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() { @Override - public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration, - long statusBarAnimationStartTime, long statusBarAnimationDuration) { - return handleStartTransitionForKeyguardLw(keyguardGoingAway, duration); + public int onAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration, long statusBarAnimationStartTime, + long statusBarAnimationDuration) { + // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI + // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't + // need to call IKeyguardService#keyguardGoingAway here. + return handleStartTransitionForKeyguardLw(keyguardGoingAway + && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation, + keyguardOccluding, duration); } @Override public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) { - handleStartTransitionForKeyguardLw(keyguardGoingAway, 0 /* duration */); + handleStartTransitionForKeyguardLw( + keyguardGoingAway, false /* keyguardOccludingStarted */, + 0 /* duration */); } }); @@ -2415,7 +2420,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // the keyguard is being hidden. This is okay because starting windows never show // secret information. // TODO(b/113840485): Occluded may not only happen on default display - if (displayId == DEFAULT_DISPLAY && mKeyguardOccluded) { + if (displayId == DEFAULT_DISPLAY && isKeyguardOccluded()) { windowFlags |= FLAG_SHOW_WHEN_LOCKED; } } @@ -3056,31 +3061,34 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public void onKeyguardOccludedChangedLw(boolean occluded) { - if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) { + if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing() + && !WindowManagerService.sEnableShellTransitions) { mPendingKeyguardOccluded = occluded; mKeyguardOccludedChanged = true; } else { - setKeyguardOccludedLw(occluded, false /* force */); + setKeyguardOccludedLw(occluded, false /* force */, + false /* transitionStarted */); } } @Override - public int applyKeyguardOcclusionChange() { + public int applyKeyguardOcclusionChange(boolean transitionStarted) { if (mKeyguardOccludedChanged) { if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded=" + mPendingKeyguardOccluded); - mKeyguardOccludedChanged = false; - if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */)) { + if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */, + transitionStarted)) { return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER; } } return 0; } - private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway, long duration) { - final int res = applyKeyguardOcclusionChange(); - if (res != 0) return res; - if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation && keyguardGoingAway) { + private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration) { + final int redoLayout = applyKeyguardOcclusionChange(keyguardOccluding); + if (redoLayout != 0) return redoLayout; + if (keyguardGoingAway) { if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation"); startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration); } @@ -3230,7 +3238,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { return; } - if (!mKeyguardOccluded && mKeyguardDelegate.isInputRestricted()) { + if (!isKeyguardOccluded() && mKeyguardDelegate.isInputRestricted()) { // when in keyguard restricted mode, must first verify unlock // before launching home mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() { @@ -3277,46 +3285,32 @@ public class PhoneWindowManager implements WindowManagerPolicy { mNavBarVirtualKeyHapticFeedbackEnabled = enabled; } - /** {@inheritDoc} */ - @Override - public void setKeyguardCandidateLw(WindowState win) { - mKeyguardCandidate = win; - setKeyguardOccludedLw(mKeyguardOccluded, true /* force */); - } - /** * Updates the occluded state of the Keyguard. * + * @param isOccluded Whether the Keyguard is occluded by another window. + * @param force notify the occluded status to KeyguardService and update flags even though + * occlude status doesn't change. + * @param transitionStarted {@code true} if keyguard (un)occluded transition started. * @return Whether the flags have changed and we have to redo the layout. */ - private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force) { + private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force, + boolean transitionStarted) { if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded); - final boolean wasOccluded = mKeyguardOccluded; - final boolean showing = mKeyguardDelegate.isShowing(); - final boolean changed = wasOccluded != isOccluded || force; - if (!isOccluded && changed && showing) { - mKeyguardOccluded = false; - mKeyguardDelegate.setOccluded(false, true /* animate */); - if (mKeyguardCandidate != null) { - if (!mKeyguardDelegate.hasLockscreenWallpaper()) { - mKeyguardCandidate.getAttrs().flags |= FLAG_SHOW_WALLPAPER; - } - } - return true; - } else if (isOccluded && changed && showing) { - mKeyguardOccluded = true; - mKeyguardDelegate.setOccluded(true, false /* animate */); - if (mKeyguardCandidate != null) { - mKeyguardCandidate.getAttrs().flags &= ~FLAG_SHOW_WALLPAPER; - } - return true; - } else if (changed) { - mKeyguardOccluded = isOccluded; - mKeyguardDelegate.setOccluded(isOccluded, false /* animate */); - return false; - } else { + mKeyguardOccludedChanged = false; + if (isKeyguardOccluded() == isOccluded && !force) { return false; } + + final boolean showing = mKeyguardDelegate.isShowing(); + final boolean animate = showing && !isOccluded; + // When remote animation is enabled for keyguard (un)occlude transition, KeyguardService + // uses remote animation start as a signal to update its occlusion status ,so we don't need + // to notify here. + final boolean notify = !WindowManagerService.sEnableRemoteKeyguardOccludeAnimation + || !transitionStarted; + mKeyguardDelegate.setOccluded(isOccluded, animate, notify); + return showing; } /** {@inheritDoc} */ @@ -4288,6 +4282,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { pmWakeReason)) + ")"); } + mActivityTaskManagerInternal.notifyWakingUp(); mDefaultDisplayPolicy.setAwake(true); // Since goToSleep performs these functions synchronously, we must @@ -4612,7 +4607,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public boolean isKeyguardShowingAndNotOccluded() { if (mKeyguardDelegate == null) return false; - return mKeyguardDelegate.isShowing() && !mKeyguardOccluded; + return mKeyguardDelegate.isShowing() && !isKeyguardOccluded(); } @Override @@ -4638,7 +4633,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public boolean isKeyguardOccluded() { if (mKeyguardDelegate == null) return false; - return mKeyguardOccluded; + return mKeyguardDelegate.isOccluded(); } /** {@inheritDoc} */ @@ -5330,7 +5325,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { proto.write(KEYGUARD_DRAW_COMPLETE, mDefaultDisplayPolicy.isKeyguardDrawComplete()); proto.write(WINDOW_MANAGER_DRAW_COMPLETE, mDefaultDisplayPolicy.isWindowManagerDrawComplete()); - proto.write(KEYGUARD_OCCLUDED, mKeyguardOccluded); + proto.write(KEYGUARD_OCCLUDED, isKeyguardOccluded()); proto.write(KEYGUARD_OCCLUDED_CHANGED, mKeyguardOccludedChanged); proto.write(KEYGUARD_OCCLUDED_PENDING, mPendingKeyguardOccluded); if (mKeyguardDelegate != null) { @@ -5415,7 +5410,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int key = mDisplayHomeButtonHandlers.keyAt(i); pw.println(mDisplayHomeButtonHandlers.get(key)); } - pw.print(prefix); pw.print("mKeyguardOccluded="); pw.print(mKeyguardOccluded); + pw.print(prefix); pw.print("mKeyguardOccluded="); pw.print(isKeyguardOccluded()); pw.print(" mKeyguardOccludedChanged="); pw.print(mKeyguardOccludedChanged); pw.print(" mPendingKeyguardOccluded="); pw.println(mPendingKeyguardOccluded); pw.print(prefix); pw.print("mAllowLockscreenWhenOnDisplays="); diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 510ab93e1af5..2f2f94d3b5de 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -173,8 +173,11 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { */ void onKeyguardOccludedChangedLw(boolean occluded); - /** Applies a keyguard occlusion change if one happened. */ - int applyKeyguardOcclusionChange(); + /** + * Applies a keyguard occlusion change if one happened. + * @param transitionStarted Whether keyguard (un)occlude transition is starting or not. + */ + int applyKeyguardOcclusionChange(boolean transitionStarted); /** * Interface to the Window Manager state associated with a particular @@ -725,13 +728,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId); /** - * Set or clear a window which can behave as the keyguard. - * - * @param win The window which can behave as the keyguard. - */ - void setKeyguardCandidateLw(@Nullable WindowState win); - - /** * Create and return an animation to re-display a window that was force hidden by Keyguard. */ public Animation createHiddenByKeyguardExit(boolean onWallpaper, diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index 0535af57d8ab..0080ec6cc989 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -29,7 +29,6 @@ import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardService; import com.android.server.UiThread; import com.android.server.policy.WindowManagerPolicy.OnKeyguardExitResult; -import com.android.server.wm.WindowManagerService; import java.io.PrintWriter; @@ -67,7 +66,7 @@ public class KeyguardServiceDelegate { boolean showing; boolean showingAndNotOccluded; boolean inputRestricted; - boolean occluded; + volatile boolean occluded; boolean secure; boolean dreaming; boolean systemIsReady; @@ -235,13 +234,6 @@ public class KeyguardServiceDelegate { return false; } - public boolean hasLockscreenWallpaper() { - if (mKeyguardService != null) { - return mKeyguardService.hasLockscreenWallpaper(); - } - return false; - } - public boolean hasKeyguard() { return mKeyguardState.deviceHasKeyguard; } @@ -259,19 +251,18 @@ public class KeyguardServiceDelegate { } } - /** - * @deprecated Notify occlude status change via remote animation. - */ - @Deprecated - public void setOccluded(boolean isOccluded, boolean animate) { - if (!WindowManagerService.sEnableRemoteKeyguardOccludeAnimation - && mKeyguardService != null) { + public void setOccluded(boolean isOccluded, boolean animate, boolean notify) { + if (mKeyguardService != null && notify) { if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate); mKeyguardService.setOccluded(isOccluded, animate); } mKeyguardState.occluded = isOccluded; } + public boolean isOccluded() { + return mKeyguardState.occluded; + } + public void dismiss(IKeyguardDismissCallback callback, CharSequence message) { if (mKeyguardService != null) { mKeyguardService.dismiss(callback, message); @@ -406,8 +397,7 @@ public class KeyguardServiceDelegate { } public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { - if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation - && mKeyguardService != null) { + if (mKeyguardService != null) { mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration); } } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java index 855a1ccc172d..ac650ec0f564 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -261,10 +261,6 @@ public class KeyguardServiceWrapper implements IKeyguardService { return mKeyguardStateMonitor.isTrusted(); } - public boolean hasLockscreenWallpaper() { - return mKeyguardStateMonitor.hasLockscreenWallpaper(); - } - public boolean isSecure(int userId) { return mKeyguardStateMonitor.isSecure(userId); } @@ -276,4 +272,4 @@ public class KeyguardServiceWrapper implements IKeyguardService { public void dump(String prefix, PrintWriter pw) { mKeyguardStateMonitor.dump(prefix, pw); } -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java index add0b01f1879..e6511372d62c 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java @@ -44,7 +44,6 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { private volatile boolean mSimSecure = true; private volatile boolean mInputRestricted = true; private volatile boolean mTrusted = false; - private volatile boolean mHasLockscreenWallpaper = false; private int mCurrentUserId; @@ -79,10 +78,6 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { return mTrusted; } - public boolean hasLockscreenWallpaper() { - return mHasLockscreenWallpaper; - } - @Override // Binder interface public void onShowingStateChanged(boolean showing) { mIsShowing = showing; @@ -110,11 +105,6 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { mCallback.onTrustedChanged(); } - @Override // Binder interface - public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { - mHasLockscreenWallpaper = hasLockscreenWallpaper; - } - public interface StateCallback { void onTrustedChanged(); void onShowingChanged(); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 688a3b2f1d59..8c7d257d271b 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -31,6 +31,8 @@ import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; import static android.os.PowerManagerInternal.wakefulnessToString; +import static com.android.internal.util.LatencyTracker.ACTION_TURN_ON_SCREEN; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -104,6 +106,7 @@ import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; +import com.android.internal.util.LatencyTracker; import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; import com.android.server.LockGuard; @@ -1842,6 +1845,9 @@ public final class PowerManagerService extends SystemService + ", details=" + details + ")..."); Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, groupId); + // The instrument will be timed out automatically after 2 seconds. + LatencyTracker.getInstance(mContext) + .onActionStart(ACTION_TURN_ON_SCREEN, String.valueOf(groupId)); setWakefulnessLocked(groupId, WAKEFULNESS_AWAKE, eventTime, uid, reason, opUid, opPackageName, details); @@ -3225,6 +3231,7 @@ public final class PowerManagerService extends SystemService && mDisplayGroupPowerStateMapper.getWakefulnessLocked( groupId) == WAKEFULNESS_AWAKE) { mDisplayGroupPowerStateMapper.setPoweringOnLocked(groupId, false); + LatencyTracker.getInstance(mContext).onActionEnd(ACTION_TURN_ON_SCREEN); Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, groupId); final int latencyMs = (int) (mClock.uptimeMillis() - mDisplayGroupPowerStateMapper.getLastPowerOnTimeLocked(groupId)); diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java index ef0079e0c01f..ca675973b2fd 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java @@ -35,7 +35,6 @@ import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; - import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper; import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils; import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerResultUtils; @@ -313,12 +312,12 @@ public final class PowerStatsLogger extends Handler { return mStartWallTime; } - public PowerStatsLogger(Context context, File dataStoragePath, + public PowerStatsLogger(Context context, Looper looper, File dataStoragePath, String meterFilename, String meterCacheFilename, String modelFilename, String modelCacheFilename, String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { - super(Looper.getMainLooper()); + super(looper); mStartWallTime = currentTimeMillis() - SystemClock.elapsedRealtime(); if (DEBUG) Slog.d(TAG, "mStartWallTime: " + mStartWallTime); mPowerStatsHALWrapper = powerStatsHALWrapper; diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index bb52c1dc1a29..9953ca8f9b65 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsService.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java @@ -28,6 +28,7 @@ import android.os.Binder; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; +import android.os.Looper; import android.os.UserHandle; import android.power.PowerStatsInternal; import android.util.Slog; @@ -79,6 +80,9 @@ public class PowerStatsService extends SystemService { private StatsPullAtomCallbackImpl mPullAtomCallback; @Nullable private PowerStatsInternal mPowerStatsInternal; + @Nullable + @GuardedBy("this") + private Looper mLooper; @VisibleForTesting static class Injector { @@ -127,12 +131,12 @@ public class PowerStatsService extends SystemService { } } - PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath, - String meterFilename, String meterCacheFilename, + PowerStatsLogger createPowerStatsLogger(Context context, Looper looper, + File dataStoragePath, String meterFilename, String meterCacheFilename, String modelFilename, String modelCacheFilename, String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { - return new PowerStatsLogger(context, dataStoragePath, + return new PowerStatsLogger(context, looper, dataStoragePath, meterFilename, meterCacheFilename, modelFilename, modelCacheFilename, residencyFilename, residencyCacheFilename, @@ -229,11 +233,11 @@ public class PowerStatsService extends SystemService { mDataStoragePath = mInjector.createDataStoragePath(); // Only start logger and triggers if initialization is successful. - mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, mDataStoragePath, - mInjector.createMeterFilename(), mInjector.createMeterCacheFilename(), - mInjector.createModelFilename(), mInjector.createModelCacheFilename(), - mInjector.createResidencyFilename(), mInjector.createResidencyCacheFilename(), - getPowerStatsHal()); + mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, getLooper(), + mDataStoragePath, mInjector.createMeterFilename(), + mInjector.createMeterCacheFilename(), mInjector.createModelFilename(), + mInjector.createModelCacheFilename(), mInjector.createResidencyFilename(), + mInjector.createResidencyCacheFilename(), getPowerStatsHal()); mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger); mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger); } else { @@ -245,6 +249,17 @@ public class PowerStatsService extends SystemService { return mInjector.getPowerStatsHALWrapperImpl(); } + private Looper getLooper() { + synchronized (this) { + if (mLooper == null) { + HandlerThread thread = new HandlerThread(TAG); + thread.start(); + return thread.getLooper(); + } + return mLooper; + } + } + public PowerStatsService(Context context) { this(context, new Injector()); } @@ -260,9 +275,7 @@ public class PowerStatsService extends SystemService { private final Handler mHandler; LocalService() { - HandlerThread thread = new HandlerThread(TAG); - thread.start(); - mHandler = new Handler(thread.getLooper()); + mHandler = new Handler(getLooper()); } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java index 212f81f72b24..086ebc99eff7 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java @@ -27,8 +27,6 @@ import android.os.SystemProperties; import android.util.Log; import java.util.Objects; -import java.util.Timer; -import java.util.TimerTask; /** * An {@link ISoundTriggerHw2} decorator that would enforce deadlines on all calls and reboot the @@ -38,14 +36,12 @@ public class SoundTriggerHw2Watchdog implements ISoundTriggerHw2 { private static final long TIMEOUT_MS = 3000; private static final String TAG = "SoundTriggerHw2Watchdog"; - private final @NonNull - ISoundTriggerHw2 mUnderlying; - private final @NonNull - Timer mTimer; + private final @NonNull ISoundTriggerHw2 mUnderlying; + private final @NonNull UptimeTimer mTimer; public SoundTriggerHw2Watchdog(@NonNull ISoundTriggerHw2 underlying) { mUnderlying = Objects.requireNonNull(underlying); - mTimer = new Timer("SoundTriggerHw2Watchdog"); + mTimer = new UptimeTimer("SoundTriggerHw2Watchdog"); } @Override @@ -149,21 +145,16 @@ public class SoundTriggerHw2Watchdog implements ISoundTriggerHw2 { } private class Watchdog implements AutoCloseable { - private final @NonNull - TimerTask mTask; + private final @NonNull UptimeTimer.Task mTask; // This exception is used merely for capturing a stack trace at the time of creation. private final @NonNull Exception mException = new Exception(); Watchdog() { - mTask = new TimerTask() { - @Override - public void run() { - Log.e(TAG, "HAL deadline expired. Rebooting.", mException); - rebootHal(); - } - }; - mTimer.schedule(mTask, TIMEOUT_MS); + mTask = mTimer.createTask(() -> { + Log.e(TAG, "HAL deadline expired. Rebooting.", mException); + rebootHal(); + }, TIMEOUT_MS); } @Override diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java index 2b03fe88a1ec..3fbcd93302e7 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java @@ -138,6 +138,8 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware // SoundTrigger data is treated the same as Hotword-source audio. This should incur the // HOTWORD op instead of the RECORD_AUDIO op. The RECORD_AUDIO permission is still required, // and since this is a data delivery check, soft denials aren't accepted. + // TODO(b/212458940): Find a better approach for checking the permission that doesn't + // require the client to know such details about the permissions logic. enforcePermissionForPreflight(mContext, identity, RECORD_AUDIO, /* allowSoftDenial= */ false); int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD); diff --git a/services/core/java/com/android/server/soundtrigger_middleware/UptimeTimer.java b/services/core/java/com/android/server/soundtrigger_middleware/UptimeTimer.java new file mode 100644 index 000000000000..bfcc7d840662 --- /dev/null +++ b/services/core/java/com/android/server/soundtrigger_middleware/UptimeTimer.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 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.soundtrigger_middleware; + +import android.annotation.NonNull; +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * A simple timer, similar to java.util.Timer, but using the "uptime clock". + * + * Example usage: + * UptimeTimer timer = new UptimeTimer("TimerThread"); + * UptimeTimer.Task task = timer.createTask(() -> { ... }, 100); + * ... + * // optionally, some time later: + * task.cancel(); + */ +class UptimeTimer { + private Handler mHandler = null; + + interface Task { + void cancel(); + } + + UptimeTimer(String threadName) { + new Thread(this::threadFunc, threadName).start(); + synchronized (this) { + while (mHandler == null) { + try { + wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + } + + Task createTask(@NonNull Runnable runnable, long uptimeMs) { + TaskImpl task = new TaskImpl(runnable); + mHandler.postDelayed(task, uptimeMs); + return task; + } + + private void threadFunc() { + Looper.prepare(); + synchronized (this) { + mHandler = new Handler(Looper.myLooper()); + notifyAll(); + } + Looper.loop(); + } + + private static class TaskImpl implements Task, Runnable { + private AtomicReference<Runnable> mRunnable = new AtomicReference<>(); + + TaskImpl(@NonNull Runnable runnable) { + mRunnable.set(runnable); + } + + @Override + public void cancel() { + mRunnable.set(null); + } + + @Override + public void run() { + Runnable runnable = mRunnable.get(); + if (runnable != null) { + runnable.run(); + } + } + } +} diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 61770ea1c1c2..dfff76d3a044 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -29,6 +29,7 @@ import static android.net.NetworkIdentity.OEM_PAID; import static android.net.NetworkIdentity.OEM_PRIVATE; import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; import static android.net.NetworkStats.METERED_ALL; +import static android.net.NetworkStats.METERED_YES; import static android.net.NetworkStats.ROAMING_ALL; import static android.net.NetworkTemplate.MATCH_ETHERNET; import static android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD; @@ -49,6 +50,10 @@ import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID; import static android.util.MathUtils.constrain; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__NOT_OPPORTUNISTIC; import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__OPPORTUNISTIC; import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO; @@ -67,6 +72,7 @@ import static java.util.concurrent.TimeUnit.MICROSECONDS; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.AppOpsManager.HistoricalOp; @@ -82,6 +88,7 @@ import android.app.StatsManager.PullAtomMetadata; import android.bluetooth.BluetoothActivityEnergyInfo; import android.bluetooth.BluetoothAdapter; import android.bluetooth.UidTraffic; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -734,6 +741,10 @@ public class StatsPullAtomService extends SystemService { case FrameworkStatsLog.RKP_ERROR_STATS: case FrameworkStatsLog.KEYSTORE2_CRASH_STATS: return pullKeystoreAtoms(atomTag, data); + case FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS: + return pullAccessibilityShortcutStatsLocked(atomTag, data); + case FrameworkStatsLog.ACCESSIBILITY_FLOATING_MENU_STATS: + return pullAccessibilityFloatingMenuStatsLocked(atomTag, data); default: throw new UnsupportedOperationException("Unknown tagId=" + atomTag); } @@ -930,6 +941,8 @@ public class StatsPullAtomService extends SystemService { registerKeystoreKeyOperationWithGeneralInfo(); registerRkpErrorStats(); registerKeystoreCrashStats(); + registerAccessibilityShortcutStats(); + registerAccessibilityFloatingMenuStats(); } private void initAndRegisterNetworkStatsPullers() { @@ -1340,7 +1353,7 @@ public class StatsPullAtomService extends SystemService { @Nullable private NetworkStats getUidNetworkStatsSnapshotForTransport(int transport) { final NetworkTemplate template = (transport == TRANSPORT_CELLULAR) ? NetworkTemplate.buildTemplateMobileWithRatType( - /*subscriptionId=*/null, NETWORK_TYPE_ALL) + /*subscriptionId=*/null, NETWORK_TYPE_ALL, METERED_YES) : NetworkTemplate.buildTemplateWifiWildcard(); return getUidNetworkStatsSnapshotForTemplate(template, /*includeTags=*/false); } @@ -1380,7 +1393,8 @@ public class StatsPullAtomService extends SystemService { final List<NetworkStatsExt> ret = new ArrayList<>(); for (final int ratType : getAllCollapsedRatTypes()) { final NetworkTemplate template = - buildTemplateMobileWithRatType(subInfo.subscriberId, ratType); + buildTemplateMobileWithRatType(subInfo.subscriberId, ratType, + METERED_YES); final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate(template, /*includeTags=*/false); if (stats != null) { @@ -4150,6 +4164,26 @@ public class StatsPullAtomService extends SystemService { mStatsCallbackImpl); } + private void registerAccessibilityShortcutStats() { + int tagId = FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS; + mStatsManager.setPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + DIRECT_EXECUTOR, + mStatsCallbackImpl + ); + } + + private void registerAccessibilityFloatingMenuStats() { + int tagId = FrameworkStatsLog.ACCESSIBILITY_FLOATING_MENU_STATS; + mStatsManager.setPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + DIRECT_EXECUTOR, + mStatsCallbackImpl + ); + } + int parseKeystoreStorageStats(KeystoreAtom[] atoms, List<StatsEvent> pulledData) { for (KeystoreAtom atomWrapper : atoms) { if (atomWrapper.payload.getTag() != KeystoreAtomPayload.storageStats) { @@ -4341,6 +4375,158 @@ public class StatsPullAtomService extends SystemService { } } + int pullAccessibilityShortcutStatsLocked(int atomTag, List<StatsEvent> pulledData) { + UserManager userManager = mContext.getSystemService(UserManager.class); + if (userManager == null) { + return StatsManager.PULL_SKIP; + } + final long token = Binder.clearCallingIdentity(); + try { + final ContentResolver resolver = mContext.getContentResolver(); + final int hardware_shortcut_type = + FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY; + final int triple_tap_shortcut = + FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP; + for (UserInfo userInfo : userManager.getUsers()) { + final int userId = userInfo.getUserHandle().getIdentifier(); + + if (isAccessibilityShortcutUser(mContext, userId)) { + final int software_shortcut_type = convertToAccessibilityShortcutType( + Settings.Secure.getIntForUser(resolver, + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 0, userId)); + final String software_shortcut_list = Settings.Secure.getStringForUser(resolver, + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId); + final int software_shortcut_service_num = countAccessibilityServices( + software_shortcut_list); + + final String hardware_shortcut_list = Settings.Secure.getStringForUser(resolver, + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId); + final int hardware_shortcut_service_num = countAccessibilityServices( + hardware_shortcut_list); + + // only allow magnification to use it for now + final int triple_tap_service_num = Settings.Secure.getIntForUser(resolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userId); + + pulledData.add( + FrameworkStatsLog.buildStatsEvent(atomTag, + software_shortcut_type, software_shortcut_service_num, + hardware_shortcut_type, hardware_shortcut_service_num, + triple_tap_shortcut, triple_tap_service_num)); + } + } + } catch (RuntimeException e) { + Slog.e(TAG, "pulling accessibility shortcuts stats failed at getUsers", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + + int pullAccessibilityFloatingMenuStatsLocked(int atomTag, List<StatsEvent> pulledData) { + UserManager userManager = mContext.getSystemService(UserManager.class); + if (userManager == null) { + return StatsManager.PULL_SKIP; + } + final long token = Binder.clearCallingIdentity(); + try { + final ContentResolver resolver = mContext.getContentResolver(); + final int defaultSize = 0; + final int defaultIconType = 0; + final int defaultFadeEnabled = 1; + final float defaultOpacity = 0.55f; + + for (UserInfo userInfo : userManager.getUsers()) { + final int userId = userInfo.getUserHandle().getIdentifier(); + + if (isAccessibilityFloatingMenuUser(mContext, userId)) { + final int size = Settings.Secure.getIntForUser(resolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, defaultSize, userId); + final int type = Settings.Secure.getIntForUser(resolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE, + defaultIconType, userId); + final boolean fadeEnabled = (Settings.Secure.getIntForUser(resolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, + defaultFadeEnabled, userId)) == 1; + final float opacity = Settings.Secure.getFloatForUser(resolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, + defaultOpacity, userId); + + pulledData.add( + FrameworkStatsLog.buildStatsEvent(atomTag, size, type, fadeEnabled, + opacity)); + } + } + } catch (RuntimeException e) { + Slog.e(TAG, "pulling accessibility floating menu stats failed at getUsers", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + + /** + * Counts how many accessibility services (including features) there are in the colon-separated + * string list. + * + * @param semicolonList colon-separated string, it should be + * {@link Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS} or + * {@link Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE}. + * @return The number of accessibility services + */ + private int countAccessibilityServices(String semicolonList) { + if (TextUtils.isEmpty(semicolonList)) { + return 0; + } + final int semiColonNums = (int) semicolonList.chars().filter(ch -> ch == ':').count(); + return TextUtils.isEmpty(semicolonList) ? 0 : semiColonNums + 1; + } + + private boolean isAccessibilityShortcutUser(Context context, @UserIdInt int userId) { + final ContentResolver resolver = context.getContentResolver(); + + final String software_shortcut_list = Settings.Secure.getStringForUser(resolver, + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId); + final String hardware_shortcut_list = Settings.Secure.getStringForUser(resolver, + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId); + final boolean hardware_shortcut_dialog_shown = Settings.Secure.getIntForUser(resolver, + Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId) == 1; + final boolean software_shortcut_enabled = !TextUtils.isEmpty(software_shortcut_list); + final boolean hardware_shortcut_enabled = + hardware_shortcut_dialog_shown && !TextUtils.isEmpty(hardware_shortcut_list); + final boolean triple_tap_shortcut_enabled = Settings.Secure.getIntForUser(resolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userId) == 1; + + return software_shortcut_enabled || hardware_shortcut_enabled + || triple_tap_shortcut_enabled; + } + + private boolean isAccessibilityFloatingMenuUser(Context context, @UserIdInt int userId) { + final ContentResolver resolver = context.getContentResolver(); + final int mode = Settings.Secure.getIntForUser(resolver, + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 0, userId); + final String software_string = Settings.Secure.getStringForUser(resolver, + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId); + + return (mode == Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) + && !TextUtils.isEmpty(software_string); + } + + private int convertToAccessibilityShortcutType(int shortcutType) { + switch (shortcutType) { + case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR: + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON; + case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU: + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; + case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE: + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; + default: + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; + } + } + // Thermal event received from vendor thermal management subsystem private static final class ThermalEventListener extends IThermalEventListener.Stub { @Override diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index a436e6b3787b..411f3dcc1eb6 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -23,6 +23,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.view.InsetsState.InternalInsetsType; +import android.view.InsetsVisibilities; import android.view.WindowInsetsController.Appearance; import android.view.WindowInsetsController.Behavior; @@ -132,10 +133,11 @@ public interface StatusBarManagerInternal { /** @see com.android.internal.statusbar.IStatusBar#onSystemBarAttributesChanged */ void onSystemBarAttributesChanged(int displayId, @Appearance int appearance, AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, - @Behavior int behavior, boolean isFullscreen); + @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName); /** @see com.android.internal.statusbar.IStatusBar#showTransient */ - void showTransient(int displayId, @InternalInsetsType int[] types); + void showTransient(int displayId, @InternalInsetsType int[] types, + boolean isGestureOnSystemBar); /** @see com.android.internal.statusbar.IStatusBar#abortTransient */ void abortTransient(int displayId, @InternalInsetsType int[] types); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 45b83cf0454d..92d8d2f03841 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -60,6 +60,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.view.InsetsState.InternalInsetsType; +import android.view.InsetsVisibilities; import android.view.WindowInsetsController.Appearance; import android.view.WindowInsetsController.Behavior; @@ -527,23 +528,25 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance, AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, - @Behavior int behavior, boolean isFullscreen) { + @Behavior int behavior, InsetsVisibilities requestedVisibilities, + String packageName) { getUiState(displayId).setBarAttributes(appearance, appearanceRegions, - navbarColorManagedByIme, behavior, isFullscreen); + navbarColorManagedByIme, behavior, requestedVisibilities, packageName); if (mBar != null) { try { mBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions, - navbarColorManagedByIme, behavior, isFullscreen); + navbarColorManagedByIme, behavior, requestedVisibilities, packageName); } catch (RemoteException ex) { } } } @Override - public void showTransient(int displayId, @InternalInsetsType int[] types) { + public void showTransient(int displayId, @InternalInsetsType int[] types, + boolean isGestureOnSystemBar) { getUiState(displayId).showTransient(types); if (mBar != null) { try { - mBar.showTransient(displayId, types); + mBar.showTransient(displayId, types, isGestureOnSystemBar); } catch (RemoteException ex) { } } } @@ -1105,13 +1108,14 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D return state; } - private class UiState { + private static class UiState { private @Appearance int mAppearance = 0; private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0]; - private ArraySet<Integer> mTransientBarTypes = new ArraySet<>(); + private final ArraySet<Integer> mTransientBarTypes = new ArraySet<>(); private boolean mNavbarColorManagedByIme = false; private @Behavior int mBehavior; - private boolean mFullscreen = false; + private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); + private String mPackageName = "none"; private int mDisabled1 = 0; private int mDisabled2 = 0; private int mImeWindowVis = 0; @@ -1121,12 +1125,14 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private void setBarAttributes(@Appearance int appearance, AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, - @Behavior int behavior, boolean isFullscreen) { + @Behavior int behavior, InsetsVisibilities requestedVisibilities, + String packageName) { mAppearance = appearance; mAppearanceRegions = appearanceRegions; mNavbarColorManagedByIme = navbarColorManagedByIme; mBehavior = behavior; - mFullscreen = isFullscreen; + mRequestedVisibilities = requestedVisibilities; + mPackageName = packageName; } private void showTransient(@InternalInsetsType int[] types) { @@ -1246,8 +1252,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis, state.mImeBackDisposition, state.mShowImeSwitcher, gatherDisableActionsLocked(mCurrentUserId, 2), state.mImeToken, - state.mNavbarColorManagedByIme, state.mBehavior, state.mFullscreen, - transientBarTypes); + state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibilities, + state.mPackageName, transientBarTypes); } } diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index e0cc8e182079..f29c40f74353 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -39,10 +39,13 @@ import android.net.vcn.VcnConfig; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnManager.VcnErrorCode; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Message; import android.os.ParcelUuid; import android.provider.Settings; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; @@ -57,6 +60,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -148,6 +152,10 @@ public class Vcn extends Handler { @NonNull private final VcnContentResolver mContentResolver; @NonNull private final ContentObserver mMobileDataSettingsObserver; + @NonNull + private final Map<Integer, VcnUserMobileDataStateListener> mMobileDataStateListeners = + new ArrayMap<>(); + /** * Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs. * @@ -221,6 +229,9 @@ public class Vcn extends Handler { // Update mIsMobileDataEnabled before starting handling of NetworkRequests. mIsMobileDataEnabled = getMobileDataStatus(); + // Register mobile data state listeners. + updateMobileDataStateListeners(); + // Register to receive cached and future NetworkRequests mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener); } @@ -348,6 +359,12 @@ public class Vcn extends Handler { gatewayConnection.teardownAsynchronously(); } + // Unregister MobileDataStateListeners + for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) { + getTelephonyManager().unregisterTelephonyCallback(listener); + } + mMobileDataStateListeners.clear(); + mCurrentStatus = VCN_STATUS_CODE_INACTIVE; } @@ -454,11 +471,40 @@ public class Vcn extends Handler { gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot); } + updateMobileDataStateListeners(); + // Update the mobile data state after updating the subscription snapshot as a change in // subIds for a subGroup may affect the mobile data state. handleMobileDataToggled(); } + private void updateMobileDataStateListeners() { + final Set<Integer> subIdsInGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup); + final HandlerExecutor executor = new HandlerExecutor(this); + + // Register new callbacks + for (int subId : subIdsInGroup) { + if (!mMobileDataStateListeners.containsKey(subId)) { + final VcnUserMobileDataStateListener listener = + new VcnUserMobileDataStateListener(); + + getTelephonyManagerForSubid(subId).registerTelephonyCallback(executor, listener); + mMobileDataStateListeners.put(subId, listener); + } + } + + // Unregister old callbacks + Iterator<Entry<Integer, VcnUserMobileDataStateListener>> iterator = + mMobileDataStateListeners.entrySet().iterator(); + while (iterator.hasNext()) { + final Entry<Integer, VcnUserMobileDataStateListener> entry = iterator.next(); + if (!subIdsInGroup.contains(entry.getKey())) { + getTelephonyManager().unregisterTelephonyCallback(entry.getValue()); + iterator.remove(); + } + } + } + private void handleMobileDataToggled() { final boolean oldMobileDataEnabledStatus = mIsMobileDataEnabled; mIsMobileDataEnabled = getMobileDataStatus(); @@ -493,11 +539,8 @@ public class Vcn extends Handler { } private boolean getMobileDataStatus() { - final TelephonyManager genericTelMan = - mVcnContext.getContext().getSystemService(TelephonyManager.class); - for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { - if (genericTelMan.createForSubscriptionId(subId).isDataEnabled()) { + if (getTelephonyManagerForSubid(subId).isDataEnabled()) { return true; } } @@ -517,6 +560,14 @@ public class Vcn extends Handler { return request.canBeSatisfiedBy(builder.build()); } + private TelephonyManager getTelephonyManager() { + return mVcnContext.getContext().getSystemService(TelephonyManager.class); + } + + private TelephonyManager getTelephonyManagerForSubid(int subid) { + return getTelephonyManager().createForSubscriptionId(subid); + } + private String getLogPrefix() { return "[" + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup) @@ -670,6 +721,16 @@ public class Vcn extends Handler { } } + @VisibleForTesting(visibility = Visibility.PRIVATE) + class VcnUserMobileDataStateListener extends TelephonyCallback + implements TelephonyCallback.UserMobileDataStateListener { + + @Override + public void onUserMobileDataStateChanged(boolean enabled) { + sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED)); + } + } + /** External dependencies used by Vcn, for injection in tests */ @VisibleForTesting(visibility = Visibility.PRIVATE) public static class Dependencies { diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 7dec4e785f5c..51c3b33b0e79 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -77,6 +77,7 @@ import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.Process; import android.os.SystemClock; +import android.provider.Settings; import android.util.ArraySet; import android.util.Slog; @@ -782,8 +783,19 @@ public class VcnGatewayConnection extends StateMachine { // TODO(b/179091925): Move the delayed-message handling to BaseState // If underlying is null, all underlying networks have been lost. Disconnect VCN after a - // timeout. + // timeout (or immediately if in airplane mode, since the device user has indicated that + // the radios should all be turned off). if (underlying == null) { + if (mDeps.isAirplaneModeOn(mVcnContext)) { + sendMessageAndAcquireWakeLock( + EVENT_UNDERLYING_NETWORK_CHANGED, + TOKEN_ALL, + new EventUnderlyingNetworkChangedInfo(null)); + sendDisconnectRequestedAndAcquireWakelock( + DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */); + return; + } + setDisconnectRequestAlarm(); } else { // Received a new Network so any previous alarm is irrelevant - cancel + clear it, @@ -2414,6 +2426,12 @@ public class VcnGatewayConnection extends StateMachine { validationStatusCallback); } + /** Checks if airplane mode is enabled. */ + public boolean isAirplaneModeOn(@NonNull VcnContext vcnContext) { + return Settings.Global.getInt(vcnContext.getContext().getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) != 0; + } + /** Gets the elapsed real time since boot, in millis. */ public long getElapsedRealTime() { return SystemClock.elapsedRealtime(); diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index e447a23cf331..245f4538a076 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -44,9 +44,11 @@ final class Vibration { enum Status { RUNNING, FINISHED, + FINISHED_UNEXPECTED, // Didn't terminate in the usual way. FORWARDED_TO_INPUT_DEVICES, CANCELLED, IGNORED_ERROR_APP_OPS, + IGNORED_ERROR_TOKEN, IGNORED, IGNORED_APP_OPS, IGNORED_BACKGROUND, diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index 0f4f58b32c1b..25321c18bb3c 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -47,6 +47,7 @@ import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.NoSuchElementException; import java.util.PriorityQueue; import java.util.Queue; @@ -98,7 +99,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } private final Object mLock = new Object(); - private final WorkSource mWorkSource = new WorkSource(); + private final WorkSource mWorkSource; private final PowerManager.WakeLock mWakeLock; private final IBatteryStats mBatteryStatsService; private final VibrationSettings mVibrationSettings; @@ -110,6 +111,8 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private volatile boolean mStop; private volatile boolean mForceStop; + // Variable only set and read in main thread. + private boolean mCalledVibrationCompleteCallback = false; VibrationThread(Vibration vib, VibrationSettings vibrationSettings, DeviceVibrationEffectAdapter effectAdapter, @@ -119,9 +122,8 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { mVibrationSettings = vibrationSettings; mDeviceEffectAdapter = effectAdapter; mCallbacks = callbacks; + mWorkSource = new WorkSource(mVibration.uid); mWakeLock = wakeLock; - mWorkSource.set(vib.uid); - mWakeLock.setWorkSource(mWorkSource); mBatteryStatsService = batteryStatsService; CombinedVibration effect = vib.getEffect(); @@ -151,17 +153,53 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { @Override public void run() { - Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY); + // Structured to guarantee the vibrators completed and released callbacks at the end of + // thread execution. Both of these callbacks are exclusively called from this thread. + try { + try { + Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY); + runWithWakeLock(); + } finally { + clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED); + } + } finally { + mCallbacks.onVibratorsReleased(); + } + } + + /** Runs the VibrationThread ensuring that the wake lock is acquired and released. */ + private void runWithWakeLock() { + mWakeLock.setWorkSource(mWorkSource); mWakeLock.acquire(); try { + runWithWakeLockAndDeathLink(); + } finally { + mWakeLock.release(); + } + } + + /** + * Runs the VibrationThread with the binder death link, handling link/unlink failures. + * Called from within runWithWakeLock. + */ + private void runWithWakeLockAndDeathLink() { + try { mVibration.token.linkToDeath(this, 0); - playVibration(); - mCallbacks.onVibratorsReleased(); } catch (RemoteException e) { Slog.e(TAG, "Error linking vibration to token death", e); + clientVibrationCompleteIfNotAlready(Vibration.Status.IGNORED_ERROR_TOKEN); + return; + } + // Ensure that the unlink always occurs now. + try { + // This is the actual execution of the vibration. + playVibration(); } finally { - mVibration.token.unlinkToDeath(this, 0); - mWakeLock.release(); + try { + mVibration.token.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Slog.wtf(TAG, "Failed to unlink token", e); + } } } @@ -219,6 +257,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } } + // Indicate that the vibration is complete. This can be called multiple times only for + // convenience of handling error conditions - an error after the client is complete won't + // affect the status. + private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) { + if (!mCalledVibrationCompleteCallback) { + mCalledVibrationCompleteCallback = true; + mCallbacks.onVibrationCompleted(mVibration.id, completedStatus); + } + } + private void playVibration() { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration"); try { @@ -226,7 +274,6 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { final int sequentialEffectSize = sequentialEffect.getEffects().size(); mStepQueue.offer(new StartVibrateStep(sequentialEffect)); - Vibration.Status status = null; while (!mStepQueue.isEmpty()) { long waitTime; synchronized (mLock) { @@ -242,13 +289,12 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { if (waitTime <= 0) { mStepQueue.consumeNext(); } - Vibration.Status currentStatus = mStop ? Vibration.Status.CANCELLED + Vibration.Status status = mStop ? Vibration.Status.CANCELLED : mStepQueue.calculateVibrationStatus(sequentialEffectSize); - if (status == null && currentStatus != Vibration.Status.RUNNING) { + if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) { // First time vibration stopped running, start clean-up tasks and notify // callback immediately. - status = currentStatus; - mCallbacks.onVibrationCompleted(mVibration.id, status); + clientVibrationCompleteIfNotAlready(status); if (status == Vibration.Status.CANCELLED) { mStepQueue.cancel(); } @@ -256,19 +302,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { if (mForceStop) { // Cancel every step and stop playing them right away, even clean-up steps. mStepQueue.cancelImmediately(); + clientVibrationCompleteIfNotAlready(Vibration.Status.CANCELLED); break; } } - - if (status == null) { - status = mStepQueue.calculateVibrationStatus(sequentialEffectSize); - if (status == Vibration.Status.RUNNING) { - Slog.w(TAG, "Something went wrong, step queue completed but vibration status" - + " is still RUNNING for vibration " + mVibration.id); - status = Vibration.Status.FINISHED; - } - mCallbacks.onVibrationCompleted(mVibration.id, status); - } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 2a47512bb147..730766275f4a 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -1319,24 +1319,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return IExternalVibratorService.SCALE_MUTE; } - int mode = checkAppOpModeLocked(vib.getUid(), vib.getPackage(), - vib.getVibrationAttributes()); - if (mode != AppOpsManager.MODE_ALLOWED) { - ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib); - vibHolder.scale = IExternalVibratorService.SCALE_MUTE; - if (mode == AppOpsManager.MODE_ERRORED) { - Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid()); - endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS); - } else { - endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS); - } - return vibHolder.scale; - } - ExternalVibrationHolder cancelingExternalVibration = null; VibrationThread cancelingVibration = null; int scale; synchronized (mLock) { + Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked( + vib.getUid(), vib.getPackage(), vib.getVibrationAttributes()); + if (ignoreStatus != null) { + ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib); + vibHolder.scale = IExternalVibratorService.SCALE_MUTE; + endVibrationLocked(vibHolder, ignoreStatus); + return vibHolder.scale; + } if (mCurrentExternalVibration != null && mCurrentExternalVibration.externalVibration.equals(vib)) { // We are already playing this external vibration, so we can return the same diff --git a/services/core/java/com/android/server/vr/Vr2dDisplay.java b/services/core/java/com/android/server/vr/Vr2dDisplay.java index a713e5b13667..39d7a1555dfc 100644 --- a/services/core/java/com/android/server/vr/Vr2dDisplay.java +++ b/services/core/java/com/android/server/vr/Vr2dDisplay.java @@ -302,7 +302,8 @@ class Vr2dDisplay { builder.setUniqueId(UNIQUE_DISPLAY_ID); builder.setFlags(flags); mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */, - builder.build(), null /* callback */, null /* handler */); + builder.build(), null /* callback */, null /* handler */, + null /* windowContext */); if (mVirtualDisplay != null) { updateDisplayId(mVirtualDisplay.getDisplay().getDisplayId()); diff --git a/services/core/java/com/android/server/wallpaper/LocalColorRepository.java b/services/core/java/com/android/server/wallpaper/LocalColorRepository.java new file mode 100644 index 000000000000..e6265f4dbd39 --- /dev/null +++ b/services/core/java/com/android/server/wallpaper/LocalColorRepository.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2021 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.wallpaper; + +import android.app.ILocalWallpaperColorConsumer; +import android.graphics.RectF; +import android.os.IBinder; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * Manages the lifecycle of local wallpaper color callbacks and their interested wallpaper regions. + */ +public class LocalColorRepository { + /** + * Maps local wallpaper color callbacks' binders to their interested wallpaper regions, which + * are stored in a map of display Ids to wallpaper regions. + * binder callback -> [display id: int] -> areas + */ + ArrayMap<IBinder, SparseArray<ArraySet<RectF>>> mLocalColorAreas = new ArrayMap(); + RemoteCallbackList<ILocalWallpaperColorConsumer> mCallbacks = new RemoteCallbackList(); + + /** + * Add areas to a consumer + * @param consumer + * @param areas + * @param displayId + */ + public void addAreas(ILocalWallpaperColorConsumer consumer, List<RectF> areas, int displayId) { + IBinder binder = consumer.asBinder(); + SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.get(binder); + ArraySet<RectF> displayAreas = null; + if (displays == null) { + try { + consumer.asBinder().linkToDeath(() -> + mLocalColorAreas.remove(consumer.asBinder()), 0); + } catch (RemoteException e) { + e.printStackTrace(); + } + displays = new SparseArray<>(); + mLocalColorAreas.put(binder, displays); + } else { + displayAreas = displays.get(displayId); + } + if (displayAreas == null) { + displayAreas = new ArraySet(areas); + displays.put(displayId, displayAreas); + } + + for (int i = 0; i < areas.size(); i++) { + displayAreas.add(areas.get(i)); + } + mCallbacks.register(consumer); + } + + /** + * remove an area for a consumer + * @param consumer + * @param areas + * @param displayId + * @return the areas that are removed from all callbacks + */ + public List<RectF> removeAreas(ILocalWallpaperColorConsumer consumer, List<RectF> areas, + int displayId) { + IBinder binder = consumer.asBinder(); + SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.get(binder); + ArraySet<RectF> registeredAreas = null; + if (displays != null) { + registeredAreas = displays.get(displayId); + if (registeredAreas == null) { + mCallbacks.unregister(consumer); + } else { + for (int i = 0; i < areas.size(); i++) { + registeredAreas.remove(areas.get(i)); + } + if (registeredAreas.size() == 0) { + displays.remove(displayId); + } + } + if (displays.size() == 0) { + mLocalColorAreas.remove(binder); + mCallbacks.unregister(consumer); + } + } else { + mCallbacks.unregister(consumer); + } + ArraySet<RectF> purged = new ArraySet<>(areas); + for (int i = 0; i < mLocalColorAreas.size(); i++) { + for (int j = 0; j < mLocalColorAreas.valueAt(i).size(); j++) { + for (int k = 0; k < mLocalColorAreas.valueAt(i).valueAt(j).size(); k++) { + purged.remove(mLocalColorAreas.valueAt(i).valueAt(j).valueAt(k)); + } + } + } + return new ArrayList(purged); + } + + /** + * Return the local areas by display id + * @param displayId + * @return + */ + public List<RectF> getAreasByDisplayId(int displayId) { + ArrayList<RectF> areas = new ArrayList(); + for (int i = 0; i < mLocalColorAreas.size(); i++) { + SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.valueAt(i); + if (displays == null) continue; + ArraySet<RectF> displayAreas = displays.get(displayId); + if (displayAreas == null) continue; + for (int j = 0; j < displayAreas.size(); j++) { + areas.add(displayAreas.valueAt(j)); + } + } + return areas; + } + + /** + * invoke a callback for each area of interest + * @param callback + * @param area + * @param displayId + */ + public void forEachCallback(Consumer<ILocalWallpaperColorConsumer> callback, + RectF area, int displayId) { + mCallbacks.broadcast(cb -> { + IBinder binder = cb.asBinder(); + SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.get(binder); + if (displays == null) return; + ArraySet<RectF> displayAreas = displays.get(displayId); + if (displayAreas != null && displayAreas.contains(area)) callback.accept(cb); + }); + } + + /** + * For testing + * @param callback + * @return if the callback is registered + */ + @VisibleForTesting + protected boolean isCallbackAvailable(ILocalWallpaperColorConsumer callback) { + return mLocalColorAreas.get(callback.asBinder()) != null; + } +} diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 2f353d19b5df..6fda72e1267b 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -89,8 +89,6 @@ import android.service.wallpaper.IWallpaperService; import android.service.wallpaper.WallpaperService; import android.system.ErrnoException; import android.system.Os; -import android.util.ArrayMap; -import android.util.ArraySet; import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; @@ -881,12 +879,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray(); private int mCurrentUserId = UserHandle.USER_NULL; private boolean mInAmbientMode; - private ArrayMap<IBinder, ArraySet<RectF>> mLocalColorCallbackAreas = - new ArrayMap<>(); - private ArrayMap<RectF, RemoteCallbackList<ILocalWallpaperColorConsumer>> - mLocalColorAreaCallbacks = new ArrayMap<>(); - private ArrayMap<Integer, ArraySet<RectF>> mLocalColorDisplayIdAreas = new ArrayMap<>(); - private ArrayMap<IBinder, Integer> mLocalColorCallbackDisplayId = new ArrayMap<>(); + private LocalColorRepository mLocalColorRepo = new LocalColorRepository(); static class WallpaperData { @@ -1305,24 +1298,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public void onLocalWallpaperColorsChanged(RectF area, WallpaperColors colors, int displayId) { forEachDisplayConnector(displayConnector -> { - if (displayConnector.mDisplayId == displayId) { - RemoteCallbackList<ILocalWallpaperColorConsumer> callbacks; - ArrayMap<IBinder, Integer> callbackDisplayIds; - synchronized (mLock) { - callbacks = mLocalColorAreaCallbacks.get(area); - callbackDisplayIds = new ArrayMap<>(mLocalColorCallbackDisplayId); + Consumer<ILocalWallpaperColorConsumer> callback = cb -> { + try { + cb.onColorsChanged(area, colors); + } catch (RemoteException e) { + e.printStackTrace(); } - if (callbacks == null) return; - callbacks.broadcast(c -> { - try { - Integer targetDisplayId = - callbackDisplayIds.get(c.asBinder()); - if (targetDisplayId == null) return; - if (targetDisplayId == displayId) c.onColorsChanged(area, colors); - } catch (RemoteException e) { - e.printStackTrace(); - } - }); + }; + synchronized (mLock) { + // it is safe to make an IPC call since it is one way (returns immediately) + mLocalColorRepo.forEachCallback(callback, area, displayId); } }); } @@ -1491,10 +1476,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.w(TAG, "Failed to request wallpaper colors", e); } - ArraySet<RectF> areas = mLocalColorDisplayIdAreas.get(displayId); + List<RectF> areas = mLocalColorRepo.getAreasByDisplayId(displayId); if (areas != null && areas.size() != 0) { try { - connector.mEngine.addLocalColorsAreas(new ArrayList<>(areas)); + connector.mEngine.addLocalColorsAreas(areas); } catch (RemoteException e) { Slog.w(TAG, "Failed to register local colors areas", e); } @@ -2494,37 +2479,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } IWallpaperEngine engine = getEngine(which, userId, displayId); if (engine == null) return; - ArrayList<RectF> validAreas = new ArrayList<>(regions.size()); synchronized (mLock) { - ArraySet<RectF> areas = mLocalColorCallbackAreas.get(callback); - if (areas == null) areas = new ArraySet<>(regions.size()); - areas.addAll(regions); - mLocalColorCallbackAreas.put(callback.asBinder(), areas); + mLocalColorRepo.addAreas(callback, regions, displayId); } - for (int i = 0; i < regions.size(); i++) { - if (!LOCAL_COLOR_BOUNDS.contains(regions.get(i))) { - continue; - } - RemoteCallbackList callbacks; - synchronized (mLock) { - callbacks = mLocalColorAreaCallbacks.get( - regions.get(i)); - if (callbacks == null) { - callbacks = new RemoteCallbackList(); - mLocalColorAreaCallbacks.put(regions.get(i), callbacks); - } - mLocalColorCallbackDisplayId.put(callback.asBinder(), displayId); - ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId); - if (displayAreas == null) { - displayAreas = new ArraySet<>(1); - mLocalColorDisplayIdAreas.put(displayId, displayAreas); - } - displayAreas.add(regions.get(i)); - } - validAreas.add(regions.get(i)); - callbacks.register(callback); - } - engine.addLocalColorsAreas(validAreas); + engine.addLocalColorsAreas(regions); } @Override @@ -2539,45 +2497,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub throw new SecurityException("calling user id does not match"); } final long identity = Binder.clearCallingIdentity(); - ArrayList<RectF> purgeAreas = new ArrayList<>(); - IBinder binder = callback.asBinder(); + List<RectF> purgeAreas = null; try { synchronized (mLock) { - ArraySet<RectF> currentAreas = mLocalColorCallbackAreas.get(binder); - if (currentAreas == null) return; - currentAreas.removeAll(removeAreas); - if (currentAreas.size() == 0) { - mLocalColorCallbackDisplayId.remove(binder); - for (RectF removeArea : removeAreas) { - RemoteCallbackList<ILocalWallpaperColorConsumer> remotes = - mLocalColorAreaCallbacks.get(removeArea); - if (remotes == null) continue; - remotes.unregister(callback); - if (remotes.getRegisteredCallbackCount() == 0) { - mLocalColorAreaCallbacks.remove(removeArea); - purgeAreas.add(removeArea); - ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId); - if (displayAreas != null) { - displayAreas.remove(removeArea); - if (displayAreas.size() == 0) { - mLocalColorDisplayIdAreas.remove(displayId); - } - } - } - } - } + purgeAreas = mLocalColorRepo.removeAreas(callback, removeAreas, displayId); } - } catch (Exception e) { // ignore any exception } finally { Binder.restoreCallingIdentity(identity); } - - if (purgeAreas.size() == 0) return; IWallpaperEngine engine = getEngine(which, userId, displayId); - if (engine == null) return; - engine.removeLocalColorsAreas(purgeAreas); + if (engine == null || purgeAreas == null) return; + if (purgeAreas.size() > 0) engine.removeLocalColorsAreas(purgeAreas); } @Override @@ -3075,12 +3007,35 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + private boolean packageBelongsToUid(String packageName, int uid) { + int userId = UserHandle.getUserId(uid); + int packageUid; + try { + packageUid = mContext.getPackageManager().getPackageUidAsUser( + packageName, userId); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + return packageUid == uid; + } + + private void enforcePackageBelongsToUid(String packageName, int uid) { + if (!packageBelongsToUid(packageName, uid)) { + throw new IllegalArgumentException( + "Invalid package or package does not belong to uid:" + + uid); + } + } + /** * Certain user types do not support wallpapers (e.g. managed profiles). The check is * implemented through through the OP_WRITE_WALLPAPER AppOp. */ public boolean isWallpaperSupported(String callingPackage) { - return mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_WRITE_WALLPAPER, Binder.getCallingUid(), + final int callingUid = Binder.getCallingUid(); + enforcePackageBelongsToUid(callingPackage, callingUid); + + return mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_WRITE_WALLPAPER, callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED; } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index a24319f7a98c..38a48570ba16 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -16,13 +16,15 @@ package com.android.server.wm; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CALLBACK; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK; import static android.os.Build.IS_USER; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY; import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER; @@ -34,17 +36,21 @@ import static com.android.server.accessibility.AccessibilityTraceProto.CALLING_P import static com.android.server.accessibility.AccessibilityTraceProto.CALLING_PKG; import static com.android.server.accessibility.AccessibilityTraceProto.CALLING_STACKS; import static com.android.server.accessibility.AccessibilityTraceProto.ELAPSED_REALTIME_NANOS; +import static com.android.server.accessibility.AccessibilityTraceProto.LOGGING_TYPE; import static com.android.server.accessibility.AccessibilityTraceProto.PROCESS_NAME; import static com.android.server.accessibility.AccessibilityTraceProto.THREAD_ID_NAME; import static com.android.server.accessibility.AccessibilityTraceProto.WHERE; import static com.android.server.accessibility.AccessibilityTraceProto.WINDOW_MANAGER_SERVICE; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowTracing.WINSCOPE_EXT; import static com.android.server.wm.utils.RegionUtils.forEachRect; +import android.accessibilityservice.AccessibilityTrace; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Application; import android.content.Context; import android.content.pm.PackageManagerInternal; @@ -84,12 +90,14 @@ import android.view.SurfaceControl; import android.view.ViewConfiguration; import android.view.WindowInfo; import android.view.WindowManager; +import android.view.WindowManagerPolicyConstants; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import com.android.internal.R; import com.android.internal.os.SomeArgs; import com.android.internal.util.TraceBuffer; +import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal; @@ -99,8 +107,6 @@ import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallba import java.io.File; import java.io.IOException; import java.io.PrintWriter; -import java.nio.file.Files; -import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -116,32 +122,36 @@ final class AccessibilityController { private static final String TAG = AccessibilityController.class.getSimpleName(); private static final Object STATIC_LOCK = new Object(); - static AccessibilityControllerInternal + static AccessibilityControllerInternalImpl getAccessibilityControllerInternal(WindowManagerService service) { return AccessibilityControllerInternalImpl.getInstance(service); } - private final AccessibilityTracing mAccessibilityTracing; + private final AccessibilityControllerInternalImpl mAccessibilityTracing; private final WindowManagerService mService; private static final Rect EMPTY_RECT = new Rect(); private static final float[] sTempFloats = new float[9]; - AccessibilityController(WindowManagerService service) { - mService = service; - mAccessibilityTracing = AccessibilityTracing.getInstance(service); - } - private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>(); private SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver = new SparseArray<>(); + private SparseArray<IBinder> mFocusedWindow = new SparseArray<>(); + private int mFocusedDisplay = -1; // Set to true if initializing window population complete. private boolean mAllObserversInitialized = true; + AccessibilityController(WindowManagerService service) { + mService = service; + mAccessibilityTracing = + AccessibilityController.getAccessibilityControllerInternal(service); + } + boolean setMagnificationCallbacks(int displayId, MagnificationCallbacks callbacks) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState( + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace( TAG + ".setMagnificationCallbacks", + FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; callbacks={" + callbacks + "}"); } boolean result = false; @@ -172,25 +182,31 @@ final class AccessibilityController { /** * Sets a callback for observing which windows are touchable for the purposes - * of accessibility on specified display. + * of accessibility on specified display. When a display is reparented and becomes + * an embedded one, the {@link WindowsForAccessibilityCallback#onDisplayReparented(int)} + * will notify the accessibility framework to remove the un-used window observer of + * this embedded display. * * @param displayId The logical display id. * @param callback The callback. - * @return {@code false} if display id is not valid or an embedded display. + * @return {@code false} if display id is not valid or an embedded display when the callback + * isn't null. */ boolean setWindowsForAccessibilityCallback(int displayId, WindowsForAccessibilityCallback callback) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState( + if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { + mAccessibilityTracing.logTrace( TAG + ".setWindowsForAccessibilityCallback", + FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "displayId=" + displayId + "; callback={" + callback + "}"); } - final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId); - if (dc == null) { - return false; - } if (callback != null) { + final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId); + if (dc == null) { + return false; + } + WindowsForAccessibilityObserver observer = mWindowsForAccessibilityObserver.get(displayId); if (isEmbeddedDisplay(dc)) { @@ -209,21 +225,13 @@ final class AccessibilityController { if (Build.IS_DEBUGGABLE) { throw new IllegalStateException(errorMessage); } - removeObserverOfEmbeddedDisplay(observer); + removeObserversForEmbeddedChildDisplays(observer); mWindowsForAccessibilityObserver.remove(displayId); } observer = new WindowsForAccessibilityObserver(mService, displayId, callback); mWindowsForAccessibilityObserver.put(displayId, observer); mAllObserversInitialized &= observer.mInitialized; } else { - if (isEmbeddedDisplay(dc)) { - // If this display is an embedded one, its window observer should be removed along - // with the window observer of its parent display removed because the window - // observer of the embedded display and its parent display is the same, and would - // be removed together when stopping the window tracking of its parent display. So - // here don't need to do removing window observer of the embedded display again. - return true; - } final WindowsForAccessibilityObserver windowsForA11yObserver = mWindowsForAccessibilityObserver.get(displayId); if (windowsForA11yObserver == null) { @@ -234,16 +242,17 @@ final class AccessibilityController { throw new IllegalStateException(errorMessage); } } - removeObserverOfEmbeddedDisplay(windowsForA11yObserver); + removeObserversForEmbeddedChildDisplays(windowsForA11yObserver); mWindowsForAccessibilityObserver.remove(displayId); } return true; } void performComputeChangedWindowsNot(int displayId, boolean forceSend) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState( + if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { + mAccessibilityTracing.logTrace( TAG + ".performComputeChangedWindowsNot", + FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "displayId=" + displayId + "; forceSend=" + forceSend); } WindowsForAccessibilityObserver observer = null; @@ -260,8 +269,10 @@ final class AccessibilityController { } void setMagnificationSpec(int displayId, MagnificationSpec spec) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(TAG + ".setMagnificationSpec", + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK + | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".setMagnificationSpec", + FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "displayId=" + displayId + "; spec={" + spec + "}"); } final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); @@ -276,8 +287,9 @@ final class AccessibilityController { } void getMagnificationRegion(int displayId, Region outMagnificationRegion) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(TAG + ".getMagnificationRegion", + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".getMagnificationRegion", + FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; outMagnificationRegion={" + outMagnificationRegion + "}"); } @@ -288,9 +300,10 @@ final class AccessibilityController { } void onRectangleOnScreenRequested(int displayId, Rect rectangle) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState( + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace( TAG + ".onRectangleOnScreenRequested", + FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; rectangle={" + rectangle + "}"); } final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); @@ -301,9 +314,11 @@ final class AccessibilityController { } void onWindowLayersChanged(int displayId) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState( - TAG + ".onWindowLayersChanged", "displayId=" + displayId); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK + | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".onWindowLayersChanged", + FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, + "displayId=" + displayId); } final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); if (displayMagnifier != null) { @@ -316,15 +331,18 @@ final class AccessibilityController { } } - void onRotationChanged(DisplayContent displayContent) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(TAG + ".onRotationChanged", + void onDisplaySizeChanged(DisplayContent displayContent) { + + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK + | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".onRotationChanged", + FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "displayContent={" + displayContent + "}"); } final int displayId = displayContent.getDisplayId(); final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); if (displayMagnifier != null) { - displayMagnifier.onRotationChanged(displayContent); + displayMagnifier.onDisplaySizeChanged(displayContent); } final WindowsForAccessibilityObserver windowsForA11yObserver = mWindowsForAccessibilityObserver.get(displayId); @@ -334,8 +352,9 @@ final class AccessibilityController { } void onAppWindowTransition(int displayId, int transition) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(TAG + ".onAppWindowTransition", + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".onAppWindowTransition", + FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; transition=" + transition); } final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); @@ -346,8 +365,10 @@ final class AccessibilityController { } void onWindowTransition(WindowState windowState, int transition) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(TAG + ".onWindowTransition", + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK + | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".onWindowTransition", + FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "windowState={" + windowState + "}; transition=" + transition); } final int displayId = windowState.getDisplayId(); @@ -364,9 +385,9 @@ final class AccessibilityController { void onWindowFocusChangedNot(int displayId) { // Not relevant for the display magnifier. - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState( - TAG + ".onWindowFocusChangedNot", "displayId=" + displayId); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".onWindowFocusChangedNot", + FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "displayId=" + displayId); } WindowsForAccessibilityObserver observer = null; synchronized (mService.mGlobalLock) { @@ -426,12 +447,10 @@ final class AccessibilityController { } void onSomeWindowResizedOrMovedWithCallingUid(int callingUid, int... displayIds) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState( - TAG + ".onSomeWindowResizedOrMoved", - "displayIds={" + displayIds.toString() + "}", - "".getBytes(), - callingUid); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".onSomeWindowResizedOrMoved", + FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, + "displayIds={" + displayIds.toString() + "}", "".getBytes(), callingUid); } // Not relevant for the display magnifier. for (int i = 0; i < displayIds.length; i++) { @@ -444,9 +463,10 @@ final class AccessibilityController { } void drawMagnifiedRegionBorderIfNeeded(int displayId, SurfaceControl.Transaction t) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState( + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace( TAG + ".drawMagnifiedRegionBorderIfNeeded", + FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; transaction={" + t + "}"); } final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); @@ -457,8 +477,9 @@ final class AccessibilityController { } MagnificationSpec getMagnificationSpecForWindow(WindowState windowState) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(TAG + ".getMagnificationSpecForWindow", + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".getMagnificationSpecForWindow", + FLAGS_MAGNIFICATION_CALLBACK, "windowState={" + windowState + "}"); } final int displayId = windowState.getDisplayId(); @@ -470,17 +491,19 @@ final class AccessibilityController { } boolean hasCallbacks() { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(TAG + ".hasCallbacks"); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK + | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".hasCallbacks", + FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK); } return (mDisplayMagnifiers.size() > 0 || mWindowsForAccessibilityObserver.size() > 0); } void setForceShowMagnifiableBounds(int displayId, boolean show) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(TAG + ".setForceShowMagnifiableBounds", - "displayId=" + displayId + "; show=" + show); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".setForceShowMagnifiableBounds", + FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; show=" + show); } final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); if (displayMagnifier != null) { @@ -497,39 +520,50 @@ final class AccessibilityController { void handleWindowObserverOfEmbeddedDisplay( int embeddedDisplayId, WindowState parentWindow, int callingUid) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(TAG + ".handleWindowObserverOfEmbeddedDisplay", + if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".handleWindowObserverOfEmbeddedDisplay", + FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "embeddedDisplayId=" + embeddedDisplayId + "; parentWindowState={" - + parentWindow + "}", - "".getBytes(), - callingUid); + + parentWindow + "}", "".getBytes(), callingUid); } if (embeddedDisplayId == Display.DEFAULT_DISPLAY || parentWindow == null) { return; } - // Finds the parent display of this embedded display - final int parentDisplayId; - WindowState candidate = parentWindow; - while (candidate != null) { - parentWindow = candidate; - candidate = parentWindow.getDisplayContent().getParentWindow(); + mService.mH.sendMessage(PooledLambda.obtainMessage( + AccessibilityController::updateWindowObserverOfEmbeddedDisplay, + this, embeddedDisplayId, parentWindow)); + } + + private void updateWindowObserverOfEmbeddedDisplay(int embeddedDisplayId, + WindowState parentWindow) { + final WindowsForAccessibilityObserver windowsForA11yObserver; + + synchronized (mService.mGlobalLock) { + // Finds the parent display of this embedded display + WindowState candidate = parentWindow; + while (candidate != null) { + parentWindow = candidate; + candidate = parentWindow.getDisplayContent().getParentWindow(); + } + final int parentDisplayId = parentWindow.getDisplayId(); + // Uses the observer of parent display + windowsForA11yObserver = mWindowsForAccessibilityObserver.get(parentDisplayId); } - parentDisplayId = parentWindow.getDisplayId(); - // Uses the observer of parent display - final WindowsForAccessibilityObserver windowsForA11yObserver = - mWindowsForAccessibilityObserver.get(parentDisplayId); if (windowsForA11yObserver != null) { + windowsForA11yObserver.notifyDisplayReparented(embeddedDisplayId); windowsForA11yObserver.addEmbeddedDisplay(embeddedDisplayId); - // Replaces the observer of embedded display to the one of parent display - mWindowsForAccessibilityObserver.put(embeddedDisplayId, windowsForA11yObserver); + synchronized (mService.mGlobalLock) { + // Replaces the observer of embedded display to the one of parent display + mWindowsForAccessibilityObserver.put(embeddedDisplayId, windowsForA11yObserver); + } } } void onImeSurfaceShownChanged(WindowState windowState, boolean shown) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(TAG + ".onImeSurfaceShownChanged", - "windowState=" + windowState + "; shown=" + shown); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".onImeSurfaceShownChanged", + FLAGS_MAGNIFICATION_CALLBACK, "windowState=" + windowState + ";shown=" + shown); } final int displayId = windowState.getDisplayId(); final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); @@ -555,7 +589,7 @@ final class AccessibilityController { + "mWindowsForAccessibilityObserver=" + mWindowsForAccessibilityObserver); } - private void removeObserverOfEmbeddedDisplay(WindowsForAccessibilityObserver + private void removeObserversForEmbeddedChildDisplays(WindowsForAccessibilityObserver observerOfParentDisplay) { final IntArray embeddedDisplayIdList = observerOfParentDisplay.getAndClearEmbeddedDisplayIdList(); @@ -572,6 +606,29 @@ final class AccessibilityController { return display.getType() == Display.TYPE_VIRTUAL && dc.getParentWindow() != null; } + void onFocusChanged(InputTarget lastTarget, InputTarget newTarget) { + if (lastTarget != null) { + mFocusedWindow.remove(lastTarget.getDisplayId()); + } + if (newTarget != null) { + int displayId = newTarget.getDisplayId(); + IBinder clientBinder = newTarget.getIWindow().asBinder(); + mFocusedWindow.put(displayId, clientBinder); + } + } + + public void onDisplayRemoved(int displayId) { + mFocusedWindow.remove(displayId); + } + + public void setFocusedDisplay(int focusedDisplayId) { + mFocusedDisplay = focusedDisplayId; + } + + @Nullable IBinder getFocusedWindowToken() { + return mFocusedWindow.get(mFocusedDisplay); + } + /** * This class encapsulates the functionality related to display magnification. */ @@ -580,7 +637,7 @@ final class AccessibilityController { private static final String LOG_TAG = TAG_WITH_CLASS_NAME ? "DisplayMagnifier" : TAG_WM; private static final boolean DEBUG_WINDOW_TRANSITIONS = false; - private static final boolean DEBUG_ROTATION = false; + private static final boolean DEBUG_DISPLAY_SIZE = false; private static final boolean DEBUG_LAYERS = false; private static final boolean DEBUG_RECTANGLE_REQUESTED = false; private static final boolean DEBUG_VIEWPORT_WINDOW = false; @@ -599,7 +656,7 @@ final class AccessibilityController { private final Handler mHandler; private final DisplayContent mDisplayContent; private final Display mDisplay; - private final AccessibilityTracing mAccessibilityTracing; + private final AccessibilityControllerInternalImpl mAccessibilityTracing; private final MagnificationCallbacks mCallbacks; @@ -618,11 +675,13 @@ final class AccessibilityController { mDisplay = display; mHandler = new MyHandler(mService.mH.getLooper()); mMagnifedViewport = new MagnifiedViewport(); - mAccessibilityTracing = AccessibilityTracing.getInstance(mService); + mAccessibilityTracing = + AccessibilityController.getAccessibilityControllerInternal(mService); mLongAnimationDuration = mDisplayContext.getResources().getInteger( com.android.internal.R.integer.config_longAnimTime); - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(LOG_TAG + ".DisplayMagnifier.constructor", + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".DisplayMagnifier.constructor", + FLAGS_MAGNIFICATION_CALLBACK, "windowManagerService={" + windowManagerService + "}; displayContent={" + displayContent + "}; display={" + display + "}; callbacks={" + callbacks + "}"); @@ -630,9 +689,9 @@ final class AccessibilityController { } void setMagnificationSpec(MagnificationSpec spec) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState( - LOG_TAG + ".setMagnificationSpec", "spec={" + spec + "}"); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".setMagnificationSpec", + FLAGS_MAGNIFICATION_CALLBACK, "spec={" + spec + "}"); } mMagnifedViewport.updateMagnificationSpec(spec); mMagnifedViewport.recomputeBounds(); @@ -642,25 +701,26 @@ final class AccessibilityController { } void setForceShowMagnifiableBounds(boolean show) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState( - LOG_TAG + ".setForceShowMagnifiableBounds", "show=" + show); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".setForceShowMagnifiableBounds", + FLAGS_MAGNIFICATION_CALLBACK, "show=" + show); } mForceShowMagnifiableBounds = show; mMagnifedViewport.setMagnifiedRegionBorderShown(show, true); } boolean isForceShowingMagnifiableBounds() { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(LOG_TAG + ".isForceShowingMagnifiableBounds"); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".isForceShowingMagnifiableBounds", + FLAGS_MAGNIFICATION_CALLBACK); } return mForceShowMagnifiableBounds; } void onRectangleOnScreenRequested(Rect rectangle) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState( - LOG_TAG + ".onRectangleOnScreenRequested", "rectangle={" + rectangle + "}"); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".onRectangleOnScreenRequested", + FLAGS_MAGNIFICATION_CALLBACK, "rectangle={" + rectangle + "}"); } if (DEBUG_RECTANGLE_REQUESTED) { Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle); @@ -683,8 +743,9 @@ final class AccessibilityController { } void onWindowLayersChanged() { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(LOG_TAG + ".onWindowLayersChanged"); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace( + LOG_TAG + ".onWindowLayersChanged", FLAGS_MAGNIFICATION_CALLBACK); } if (DEBUG_LAYERS) { Slog.i(LOG_TAG, "Layers changed."); @@ -693,23 +754,24 @@ final class AccessibilityController { mService.scheduleAnimationLocked(); } - void onRotationChanged(DisplayContent displayContent) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState( - LOG_TAG + ".onRotationChanged", "displayContent={" + displayContent + "}"); + void onDisplaySizeChanged(DisplayContent displayContent) { + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".onDisplaySizeChanged", + FLAGS_MAGNIFICATION_CALLBACK, "displayContent={" + displayContent + "}"); } - if (DEBUG_ROTATION) { + if (DEBUG_DISPLAY_SIZE) { final int rotation = displayContent.getRotation(); Slog.i(LOG_TAG, "Rotation: " + Surface.rotationToString(rotation) + " displayId: " + displayContent.getDisplayId()); } - mMagnifedViewport.onRotationChanged(); - mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED); + mMagnifedViewport.onDisplaySizeChanged(); + mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED); } void onAppWindowTransition(int displayId, int transition) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(LOG_TAG + ".onAppWindowTransition", + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".onAppWindowTransition", + FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; transition=" + transition); } if (DEBUG_WINDOW_TRANSITIONS) { @@ -721,6 +783,7 @@ final class AccessibilityController { if (magnifying) { switch (transition) { case WindowManager.TRANSIT_OLD_ACTIVITY_OPEN: + case WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN: case WindowManager.TRANSIT_OLD_TASK_OPEN: case WindowManager.TRANSIT_OLD_TASK_TO_FRONT: case WindowManager.TRANSIT_OLD_WALLPAPER_OPEN: @@ -733,8 +796,9 @@ final class AccessibilityController { } void onWindowTransition(WindowState windowState, int transition) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(LOG_TAG + ".onWindowTransition", + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".onWindowTransition", + FLAGS_MAGNIFICATION_CALLBACK, "windowState={" + windowState + "}; transition=" + transition); } if (DEBUG_WINDOW_TRANSITIONS) { @@ -791,18 +855,18 @@ final class AccessibilityController { } void onImeSurfaceShownChanged(boolean shown) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState( - LOG_TAG + ".onImeSurfaceShownChanged", "shown=" + shown); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".onImeSurfaceShownChanged", + FLAGS_MAGNIFICATION_CALLBACK, "shown=" + shown); } mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED, shown ? 1 : 0, 0).sendToTarget(); } MagnificationSpec getMagnificationSpecForWindow(WindowState windowState) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(LOG_TAG + ".getMagnificationSpecForWindow", - "windowState={" + windowState + "}"); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationSpecForWindow", + FLAGS_MAGNIFICATION_CALLBACK, "windowState={" + windowState + "}"); } MagnificationSpec spec = mMagnifedViewport.getMagnificationSpec(); if (spec != null && !spec.isNop()) { @@ -814,8 +878,9 @@ final class AccessibilityController { } void getMagnificationRegion(Region outMagnificationRegion) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(LOG_TAG + ".getMagnificationRegion", + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationRegion", + FLAGS_MAGNIFICATION_CALLBACK, "outMagnificationRegion={" + outMagnificationRegion + "}"); } // Make sure we're working with the most current bounds @@ -824,25 +889,26 @@ final class AccessibilityController { } void destroy() { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(LOG_TAG + ".destroy"); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".destroy", FLAGS_MAGNIFICATION_CALLBACK); } mMagnifedViewport.destroyWindow(); } // Can be called outside of a surface transaction void showMagnificationBoundsIfNeeded() { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(LOG_TAG + ".showMagnificationBoundsIfNeeded"); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".showMagnificationBoundsIfNeeded", + FLAGS_MAGNIFICATION_CALLBACK); } mHandler.obtainMessage(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED) .sendToTarget(); } void drawMagnifiedRegionBorderIfNeeded(SurfaceControl.Transaction t) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded", - "transition={" + t + "}"); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded", + FLAGS_MAGNIFICATION_CALLBACK, "transition={" + t + "}"); } mMagnifedViewport.drawWindowIfNeeded(t); } @@ -887,7 +953,8 @@ final class AccessibilityController { if (mDisplayContext.getResources().getConfiguration().isScreenRound()) { mCircularPath = new Path(); - mDisplay.getRealSize(mScreenSize); + + getDisplaySizeLocked(mScreenSize); final int centerXY = mScreenSize.x / 2; mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW); } else { @@ -917,7 +984,7 @@ final class AccessibilityController { } void recomputeBounds() { - mDisplay.getRealSize(mScreenSize); + getDisplaySizeLocked(mScreenSize); final int screenWidth = mScreenSize.x; final int screenHeight = mScreenSize.y; @@ -942,6 +1009,8 @@ final class AccessibilityController { final int windowType = windowState.mAttrs.type; if (isExcludedWindowType(windowType) || ((windowState.mAttrs.privateFlags + & PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION) != 0) + || ((windowState.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) { continue; } @@ -1006,7 +1075,6 @@ final class AccessibilityController { } } } - visibleWindows.clear(); mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset, @@ -1043,18 +1111,16 @@ final class AccessibilityController { private boolean isExcludedWindowType(int windowType) { return windowType == TYPE_MAGNIFICATION_OVERLAY - // Omit the touch region to avoid the cut out of the magnification - // bounds because nav bar panel is unmagnifiable. - || windowType == TYPE_NAVIGATION_BAR_PANEL // Omit the touch region of window magnification to avoid the cut out of the // magnification and the magnified center of window magnification could be // in the bounds || windowType == TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; } - void onRotationChanged() { + void onDisplaySizeChanged() { // If we are showing the magnification border, hide it immediately so - // the user does not see strange artifacts during rotation. The screenshot + // the user does not see strange artifacts during display size changed caused by + // rotation or folding/unfolding the device. In the rotation case, the screenshot // used for rotation already has the border. After the rotation is complete // we will show the border. if (isMagnifying() || isForceShowingMagnifiableBounds()) { @@ -1112,6 +1178,12 @@ final class AccessibilityController { }, false /* traverseTopToBottom */ ); } + private void getDisplaySizeLocked(Point outSize) { + final Rect bounds = + mDisplayContent.getConfiguration().windowConfiguration.getBounds(); + outSize.set(bounds.width(), bounds.height()); + } + void dump(PrintWriter pw, String prefix) { mWindow.dump(pw, prefix); } @@ -1155,7 +1227,7 @@ final class AccessibilityController { final SurfaceControl.Transaction t = mService.mTransactionFactory.get(); final int layer = mService.mPolicy.getWindowLayerFromTypeLw(TYPE_MAGNIFICATION_OVERLAY) * - WindowManagerService.TYPE_LAYER_MULTIPLIER; + WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER; t.setLayer(mSurfaceControl, layer).setPosition(mSurfaceControl, 0, 0); InputMonitor.setTrustedOverlayInputInfo(mSurfaceControl, t, mDisplayContent.getDisplayId(), "Magnification Overlay"); @@ -1226,7 +1298,7 @@ final class AccessibilityController { void updateSize() { synchronized (mService.mGlobalLock) { - mDisplay.getRealSize(mScreenSize); + getDisplaySizeLocked(mScreenSize); mBlastBufferQueue.update(mSurfaceControl, mScreenSize.x, mScreenSize.y, PixelFormat.RGBA_8888); invalidate(mDirtyRect); @@ -1365,7 +1437,7 @@ final class AccessibilityController { public static final int MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED = 1; public static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2; public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3; - public static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4; + public static final int MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED = 4; public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5; public static final int MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED = 6; @@ -1397,9 +1469,8 @@ final class AccessibilityController { mCallbacks.onUserContextChanged(); } break; - case MESSAGE_NOTIFY_ROTATION_CHANGED: { - final int rotation = message.arg1; - mCallbacks.onRotationChanged(rotation); + case MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED: { + mCallbacks.onDisplaySizeChanged(); } break; case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : { @@ -1482,7 +1553,7 @@ final class AccessibilityController { private final Handler mHandler; - private final AccessibilityTracing mAccessibilityTracing; + private final AccessibilityControllerInternalImpl mAccessibilityTracing; private final WindowsForAccessibilityCallback mCallback; @@ -1502,24 +1573,26 @@ final class AccessibilityController { mCallback = callback; mDisplayId = displayId; mHandler = new MyHandler(mService.mH.getLooper()); - mAccessibilityTracing = AccessibilityTracing.getInstance(mService); + mAccessibilityTracing = + AccessibilityController.getAccessibilityControllerInternal(mService); mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration .getSendRecurringAccessibilityEventsInterval(); computeChangedWindows(true); } void performComputeChangedWindows(boolean forceSend) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(LOG_TAG + ".performComputeChangedWindows", - "forceSend=" + forceSend); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".performComputeChangedWindows", + FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "forceSend=" + forceSend); } mHandler.removeMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS); computeChangedWindows(forceSend); } void scheduleComputeChangedWindows() { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState(LOG_TAG + ".scheduleComputeChangedWindows"); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".scheduleComputeChangedWindows", + FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK); } if (!mHandler.hasMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS)) { mHandler.sendEmptyMessageDelayed(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS, @@ -1542,6 +1615,13 @@ final class AccessibilityController { mEmbeddedDisplayIdList.add(displayId); } + void notifyDisplayReparented(int embeddedDisplayId) { + // Notifies the A11y framework the display is reparented and + // becomes an embedded display for removing the un-used + // displayWindowObserver of this embedded one. + mCallback.onDisplayReparented(embeddedDisplayId); + } + boolean shellRootIsAbove(WindowState windowState, ShellRoot shellRoot) { int wsLayer = mService.mPolicy.getWindowLayerLw(windowState); int shellLayer = mService.mPolicy.getWindowLayerFromTypeLw(shellRoot.getWindowType(), @@ -1594,9 +1674,9 @@ final class AccessibilityController { * @param forceSend Send the windows the accessibility even if they haven't changed. */ void computeChangedWindows(boolean forceSend) { - if (mAccessibilityTracing.isEnabled()) { - mAccessibilityTracing.logState( - LOG_TAG + ".computeChangedWindows", "forceSend=" + forceSend); + if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".computeChangedWindows", + FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "forceSend=" + forceSend); } if (DEBUG) { Slog.i(LOG_TAG, "computeChangedWindows()"); @@ -1645,7 +1725,7 @@ final class AccessibilityController { boolean focusedWindowAdded = false; final int visibleWindowCount = visibleWindows.size(); - HashSet<Integer> skipRemainingWindowsForTasks = new HashSet<>(); + ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments = new ArrayList<>(); ArrayList<ShellRoot> shellRoots = getSortedShellRoots(dc.mShellRoots); @@ -1667,10 +1747,10 @@ final class AccessibilityController { computeWindowRegionInScreen(windowState, regionInScreen); if (windowMattersToAccessibility(windowState, regionInScreen, unaccountedSpace, - skipRemainingWindowsForTasks)) { + skipRemainingWindowsForTaskFragments)) { addPopulatedWindowInfo(windowState, regionInScreen, windows, addedWindows); updateUnaccountedSpace(windowState, regionInScreen, unaccountedSpace, - skipRemainingWindowsForTasks); + skipRemainingWindowsForTaskFragments); focusedWindowAdded |= windowState.isFocused(); } else if (isUntouchableNavigationBar(windowState, mTempRegion1)) { // If this widow is navigation bar without touchable region, accounting the @@ -1726,7 +1806,7 @@ final class AccessibilityController { private boolean windowMattersToAccessibility(WindowState windowState, Region regionInScreen, Region unaccountedSpace, - HashSet<Integer> skipRemainingWindowsForTasks) { + ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments) { final RecentsAnimationController controller = mService.getRecentsAnimationController(); if (controller != null && controller.shouldIgnoreForAccessibility(windowState)) { return false; @@ -1737,8 +1817,9 @@ final class AccessibilityController { } // If the window is part of a task that we're finished with - ignore. - final Task task = windowState.getTask(); - if (task != null && skipRemainingWindowsForTasks.contains(task.mTaskId)) { + final TaskFragment taskFragment = windowState.getTaskFragment(); + if (taskFragment != null + && skipRemainingWindowsForTaskFragments.contains(taskFragment)) { return false; } @@ -1764,7 +1845,8 @@ final class AccessibilityController { } private void updateUnaccountedSpace(WindowState windowState, Region regionInScreen, - Region unaccountedSpace, HashSet<Integer> skipRemainingWindowsForTasks) { + Region unaccountedSpace, + ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments) { if (windowState.mAttrs.type != WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) { @@ -1794,11 +1876,11 @@ final class AccessibilityController { Region.Op.REVERSE_DIFFERENCE); } - final Task task = windowState.getTask(); - if (task != null) { + final TaskFragment taskFragment = windowState.getTaskFragment(); + if (taskFragment != null) { // If the window is associated with a particular task, we can skip the // rest of the windows for that task. - skipRemainingWindowsForTasks.add(task.mTaskId); + skipRemainingWindowsForTaskFragments.add(taskFragment); } else if (!windowState.hasTapExcludeRegion()) { // If the window is not associated with a particular task, then it is // globally modal. In this case we can skip all remaining windows when @@ -1945,8 +2027,8 @@ final class AccessibilityController { private static final class AccessibilityControllerInternalImpl implements AccessibilityControllerInternal { - private static AccessibilityControllerInternal sInstance; - static AccessibilityControllerInternal getInstance(WindowManagerService service) { + private static AccessibilityControllerInternalImpl sInstance; + static AccessibilityControllerInternalImpl getInstance(WindowManagerService service) { synchronized (STATIC_LOCK) { if (sInstance == null) { sInstance = new AccessibilityControllerInternalImpl(service); @@ -1956,18 +2038,23 @@ final class AccessibilityController { } private final AccessibilityTracing mTracing; + private volatile long mEnabledTracingFlags; + private AccessibilityControllerInternalImpl(WindowManagerService service) { mTracing = AccessibilityTracing.getInstance(service); + mEnabledTracingFlags = 0L; } @Override - public void startTrace() { + public void startTrace(long loggingTypes) { + mEnabledTracingFlags = loggingTypes; mTracing.startTrace(); } @Override public void stopTrace() { mTracing.stopTrace(); + mEnabledTracingFlags = 0L; } @Override @@ -1975,19 +2062,37 @@ final class AccessibilityController { return mTracing.isEnabled(); } + boolean isTracingEnabled(long flags) { + return (flags & mEnabledTracingFlags) != 0L; + } + + void logTrace(String where, long loggingTypes) { + logTrace(where, loggingTypes, ""); + } + + void logTrace(String where, long loggingTypes, String callingParams) { + logTrace(where, loggingTypes, callingParams, "".getBytes(), Binder.getCallingUid()); + } + + void logTrace(String where, long loggingTypes, String callingParams, byte[] a11yDump, + int callingUid) { + mTracing.logState(where, loggingTypes, callingParams, a11yDump, callingUid, + new HashSet<String>(Arrays.asList("logTrace"))); + } + @Override - public void logTrace( - String where, String callingParams, byte[] a11yDump, int callingUid, - StackTraceElement[] stackTrace) { - mTracing.logState(where, callingParams, a11yDump, callingUid, stackTrace); + public void logTrace(String where, long loggingTypes, String callingParams, byte[] a11yDump, + int callingUid, StackTraceElement[] stackTrace, Set<String> ignoreStackEntries) { + mTracing.logState(where, loggingTypes, callingParams, a11yDump, callingUid, stackTrace, + ignoreStackEntries); } @Override - public void logTrace( - String where, String callingParams, byte[] a11yDump, int callingUid, - StackTraceElement[] callStack, long timeStamp, int processId, long threadId) { - mTracing.logState(where, callingParams, a11yDump, callingUid, callStack, timeStamp, - processId, threadId); + public void logTrace(String where, long loggingTypes, String callingParams, byte[] a11yDump, + int callingUid, StackTraceElement[] callStack, long timeStamp, int processId, + long threadId, Set<String> ignoreStackEntries) { + mTracing.logState(where, loggingTypes, callingParams, a11yDump, callingUid, callStack, + timeStamp, processId, threadId, ignoreStackEntries); } } @@ -2003,8 +2108,7 @@ final class AccessibilityController { } private static final int BUFFER_CAPACITY = 1024 * 1024 * 12; - private static final String TRACE_FILENAME = "/data/misc/a11ytrace/a11y_trace.pb"; - private static final String TRACE_DIRECTORY = "/data/misc/a11ytrace/"; + private static final String TRACE_FILENAME = "/data/misc/a11ytrace/a11y_trace" + WINSCOPE_EXT; private static final String TAG = "AccessibilityTracing"; private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; @@ -2034,13 +2138,6 @@ final class AccessibilityController { return; } synchronized (mLock) { - try { - Files.createDirectories(Paths.get(TRACE_DIRECTORY)); - mTraceFile.createNewFile(); - } catch (Exception e) { - Slog.e(TAG, "Error: Failed to create trace file."); - return; - } mEnabled = true; mBuffer.resetBuffer(); } @@ -2071,106 +2168,150 @@ final class AccessibilityController { /** * Write an accessibility trace log entry. */ - void logState(String where) { + void logState(String where, long loggingTypes) { if (!mEnabled) { return; } - logState(where, ""); + logState(where, loggingTypes, ""); } /** * Write an accessibility trace log entry. */ - void logState(String where, String callingParams) { + void logState(String where, long loggingTypes, String callingParams) { if (!mEnabled) { return; } - logState(where, callingParams, "".getBytes()); + logState(where, loggingTypes, callingParams, "".getBytes()); } /** * Write an accessibility trace log entry. */ - void logState(String where, String callingParams, byte[] a11yDump) { + void logState(String where, long loggingTypes, String callingParams, byte[] a11yDump) { if (!mEnabled) { return; } - logState(where, callingParams, a11yDump, Binder.getCallingUid()); + logState(where, loggingTypes, callingParams, a11yDump, Binder.getCallingUid(), + new HashSet<String>(Arrays.asList("logState"))); } /** * Write an accessibility trace log entry. */ - void logState( - String where, String callingParams, byte[] a11yDump, int callingUid) { + void logState(String where, long loggingTypes, String callingParams, byte[] a11yDump, + int callingUid, Set<String> ignoreStackEntries) { if (!mEnabled) { return; } StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); - - logState(where, callingParams, a11yDump, callingUid, stackTraceElements); + ignoreStackEntries.add("logState"); + logState(where, loggingTypes, callingParams, a11yDump, callingUid, stackTraceElements, + ignoreStackEntries); } /** * Write an accessibility trace log entry. */ - void logState(String where, String callingParams, byte[] a11yDump, int callingUid, - StackTraceElement[] stackTrace) { + void logState(String where, long loggingTypes, String callingParams, byte[] a11yDump, + int callingUid, StackTraceElement[] stackTrace, Set<String> ignoreStackEntries) { if (!mEnabled) { return; } - - log(where, callingParams, a11yDump, callingUid, stackTrace, + log(where, loggingTypes, callingParams, a11yDump, callingUid, stackTrace, SystemClock.elapsedRealtimeNanos(), Process.myPid() + ":" + Application.getProcessName(), - Thread.currentThread().getId() + ":" + Thread.currentThread().getName()); + Thread.currentThread().getId() + ":" + Thread.currentThread().getName(), + ignoreStackEntries); } /** * Write an accessibility trace log entry. */ - void logState(String where, String callingParams, byte[] a11yDump, int callingUid, - StackTraceElement[] callingStack, long timeStamp, int processId, long threadId) { + void logState(String where, long loggingTypes, String callingParams, byte[] a11yDump, + int callingUid, StackTraceElement[] callingStack, long timeStamp, int processId, + long threadId, Set<String> ignoreStackEntries) { if (!mEnabled) { return; } - log(where, callingParams, a11yDump, callingUid, callingStack, timeStamp, - String.valueOf(processId), String.valueOf(threadId)); + log(where, loggingTypes, callingParams, a11yDump, callingUid, callingStack, timeStamp, + String.valueOf(processId), String.valueOf(threadId), ignoreStackEntries); } - private String toStackTraceString(StackTraceElement[] stackTraceElements) { + private String toStackTraceString(StackTraceElement[] stackTraceElements, + Set<String> ignoreStackEntries) { + if (stackTraceElements == null) { return ""; } + StringBuilder stringBuilder = new StringBuilder(); - boolean skip = true; - for (int i = 0; i < stackTraceElements.length; i++) { - if (stackTraceElements[i].toString().contains( - AccessibilityTracing.class.getSimpleName())) { - skip = false; - } else if (!skip) { - stringBuilder.append(stackTraceElements[i].toString()).append("\n"); + int i = 0; + + // Skip the first a few elements until after any ignoreStackEntries + int firstMatch = -1; + while (i < stackTraceElements.length) { + for (String ele : ignoreStackEntries) { + if (stackTraceElements[i].toString().contains(ele)) { + // found the first stack element containing the ignorable stack entries + firstMatch = i; + break; + } + } + if (firstMatch < 0) { + // Haven't found the first match yet, continue + i++; + } else { + break; + } + } + int lastMatch = firstMatch; + if (i < stackTraceElements.length) { + i++; + // Found the first match. Now look for the last match. + while (i < stackTraceElements.length) { + for (String ele : ignoreStackEntries) { + if (stackTraceElements[i].toString().contains(ele)) { + // This is a match. Look at the next stack element. + lastMatch = i; + break; + } + } + if (lastMatch != i) { + // Found a no-match. + break; + } + i++; } } + + i = lastMatch + 1; + while (i < stackTraceElements.length) { + stringBuilder.append(stackTraceElements[i].toString()).append("\n"); + i++; + } return stringBuilder.toString(); } /** * Write the current state to the buffer */ - private void log(String where, String callingParams, byte[] a11yDump, int callingUid, - StackTraceElement[] callingStack, long timeStamp, String processName, - String threadName) { + private void log(String where, long loggingTypes, String callingParams, byte[] a11yDump, + int callingUid, StackTraceElement[] callingStack, long timeStamp, + String processName, String threadName, Set<String> ignoreStackEntries) { SomeArgs args = SomeArgs.obtain(); args.arg1 = timeStamp; - args.arg2 = where; - args.arg3 = processName; - args.arg4 = threadName; - args.arg5 = callingUid; - args.arg6 = callingParams; - args.arg7 = callingStack; - args.arg8 = a11yDump; - mHandler.obtainMessage(LogHandler.MESSAGE_LOG_TRACE_ENTRY, args).sendToTarget(); + args.arg2 = loggingTypes; + args.arg3 = where; + args.arg4 = processName; + args.arg5 = threadName; + args.arg6 = ignoreStackEntries; + args.arg7 = callingParams; + args.arg8 = callingStack; + args.arg9 = a11yDump; + + mHandler.obtainMessage( + LogHandler.MESSAGE_LOG_TRACE_ENTRY, callingUid, 0, args).sendToTarget(); } /** @@ -2199,8 +2340,6 @@ final class AccessibilityController { LocalServices.getService(PackageManagerInternal.class); long tokenOuter = os.start(ENTRY); - String callingStack = - toStackTraceString((StackTraceElement[]) args.arg7); long reportedTimeStampNanos = (long) args.arg1; long currentElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos(); @@ -2213,13 +2352,25 @@ final class AccessibilityController { os.write(ELAPSED_REALTIME_NANOS, reportedTimeStampNanos); os.write(CALENDAR_TIME, fm.format(reportedTimeMillis).toString()); - os.write(WHERE, (String) args.arg2); - os.write(PROCESS_NAME, (String) args.arg3); - os.write(THREAD_ID_NAME, (String) args.arg4); - os.write(CALLING_PKG, pmInternal.getNameForUid((int) args.arg5)); - os.write(CALLING_PARAMS, (String) args.arg6); + + long loggingTypes = (long) args.arg2; + List<String> loggingTypeNames = + AccessibilityTrace.getNamesOfLoggingTypes(loggingTypes); + + for (String type : loggingTypeNames) { + os.write(LOGGING_TYPE, type); + } + os.write(WHERE, (String) args.arg3); + os.write(PROCESS_NAME, (String) args.arg4); + os.write(THREAD_ID_NAME, (String) args.arg5); + os.write(CALLING_PKG, pmInternal.getNameForUid(message.arg1)); + os.write(CALLING_PARAMS, (String) args.arg7); + + String callingStack = toStackTraceString( + (StackTraceElement[]) args.arg8, (Set<String>) args.arg6); + os.write(CALLING_STACKS, callingStack); - os.write(ACCESSIBILITY_SERVICE, (byte[]) args.arg8); + os.write(ACCESSIBILITY_SERVICE, (byte[]) args.arg9); long tokenInner = os.start(WINDOW_MANAGER_SERVICE); synchronized (mService.mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index e02e8671f211..ee72fc8622a5 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -30,6 +30,11 @@ import static android.view.Display.INVALID_DISPLAY; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE; +import static com.android.server.wm.ActivityRecord.State.DESTROYED; +import static com.android.server.wm.ActivityRecord.State.DESTROYING; +import static com.android.server.wm.ActivityRecord.State.PAUSING; +import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS; +import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; @@ -37,10 +42,9 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH; import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller; -import static com.android.server.wm.Task.ActivityState.DESTROYED; -import static com.android.server.wm.Task.ActivityState.DESTROYING; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityTaskManager; @@ -72,6 +76,7 @@ import android.service.voice.VoiceInteractionManagerInternal; import android.util.Slog; import android.view.RemoteAnimationDefinition; import android.window.SizeConfigurationBuckets; +import android.window.TransitionInfo; import com.android.internal.app.AssistUtils; import com.android.internal.policy.IKeyguardDismissCallback; @@ -193,7 +198,7 @@ class ActivityClientController extends IActivityClientController.Stub { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityStopped"); r = ActivityRecord.isInRootTaskLocked(token); if (r != null) { - if (r.attachedToProcess() && r.isState(Task.ActivityState.RESTARTING_PROCESS)) { + if (r.attachedToProcess() && r.isState(RESTARTING_PROCESS)) { // The activity was requested to restart from // {@link #restartActivityProcessIfVisible}. restartingName = r.app.mName; @@ -540,6 +545,29 @@ class ActivityClientController extends IActivityClientController.Stub { } @Override + @Nullable + public IBinder getActivityTokenBelow(IBinder activityToken) { + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + final ActivityRecord ar = ActivityRecord.isInAnyTask(activityToken); + if (ar == null) { + return null; + } + // Exclude finishing activity. + final ActivityRecord below = ar.getTask().getActivity((r) -> !r.finishing, + ar, false /*includeBoundary*/, true /*traverseTopToBottom*/); + if (below != null && below.getUid() == ar.getUid()) { + return below.appToken.asBinder(); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + return null; + } + + @Override public ComponentName getCallingActivity(IBinder token) { synchronized (mGlobalLock) { final ActivityRecord r = getCallingRecord(token); @@ -1040,10 +1068,14 @@ class ActivityClientController extends IActivityClientController.Stub { final long origId = Binder.clearCallingIdentity(); synchronized (mGlobalLock) { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); - if (r != null && r.isState(Task.ActivityState.RESUMED, Task.ActivityState.PAUSING)) { + if (r != null && r.isState(RESUMED, PAUSING)) { r.mDisplayContent.mAppTransition.overridePendingAppTransition( packageName, enterAnim, exitAnim, null, null, r.mOverrideTaskTransition); + r.mTransitionController.setOverrideAnimation( + TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName, + enterAnim, exitAnim, r.mOverrideTaskTransition), + null /* startCallback */, null /* finishCallback */); } } Binder.restoreCallingIdentity(origId); @@ -1183,7 +1215,7 @@ class ActivityClientController extends IActivityClientController.Stub { try { final Intent baseActivityIntent; final boolean launchedFromHome; - + final boolean isLastRunningActivity; synchronized (mGlobalLock) { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); if (r == null) return; @@ -1195,22 +1227,25 @@ class ActivityClientController extends IActivityClientController.Stub { return; } - final Intent baseIntent = r.getTask().getBaseIntent(); - final boolean activityIsBaseActivity = baseIntent != null - && r.mActivityComponent.equals(baseIntent.getComponent()); - baseActivityIntent = activityIsBaseActivity ? r.intent : null; + final Task task = r.getTask(); + isLastRunningActivity = task.topRunningActivity() == r; + + final boolean isBaseActivity = r.mActivityComponent.equals(task.realActivity); + baseActivityIntent = isBaseActivity ? r.intent : null; + launchedFromHome = r.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME); } // If the activity is one of the main entry points for the application, then we should // refrain from finishing the activity and instead move it to the back to keep it in // memory. The requirements for this are: - // 1. The current activity is the base activity for the task. - // 2. a. If the activity was launched by the home process, we trust that its intent + // 1. The activity is the last running activity in the task. + // 2. The current activity is the base activity for the task. + // 3. a. If the activity was launched by the home process, we trust that its intent // was resolved, so we check if the it is a main intent for the application. // b. Otherwise, we query Package Manager to verify whether the activity is a // launcher activity for the application. - if (baseActivityIntent != null + if (baseActivityIntent != null && isLastRunningActivity && ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent)) || isLauncherActivity(baseActivityIntent.getComponent()))) { moveActivityTaskToBack(token, false /* nonRoot */); diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java new file mode 100644 index 000000000000..1c2333a6ffa4 --- /dev/null +++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 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 android.annotation.IntDef; +import android.annotation.Nullable; +import android.app.ActivityOptions; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ResolveInfo; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Callback to intercept activity starts and possibly block/redirect them. + */ +public abstract class ActivityInterceptorCallback { + /** + * Intercept the launch intent based on various signals. If an interception happened, returns + * a new/existing non-null {@link Intent} which may redirect to another activity. + * + * @return null if no interception occurred, or a non-null intent which replaces the + * existing intent. + */ + public abstract @Nullable Intent intercept(ActivityInterceptorInfo info); + + /** + * The unique id of each interceptor which determines the order it will execute in. + */ + @IntDef(suffix = { "_ORDERED_ID" }, value = { + FIRST_ORDERED_ID, + LAST_ORDERED_ID // Update this when adding new ids + }) + @Retention(RetentionPolicy.SOURCE) + public @interface OrderedId {} + + /** + * The first id, used by the framework to determine the valid range of ids. + */ + static final int FIRST_ORDERED_ID = 0; + + /** + * The final id, used by the framework to determine the valid range of ids. Update this when + * adding new ids. + */ + static final int LAST_ORDERED_ID = FIRST_ORDERED_ID; + + /** + * Data class for storing the various arguments needed for activity interception. + */ + public static final class ActivityInterceptorInfo { + public final int realCallingUid; + public final int realCallingPid; + public final int userId; + public final String callingPackage; + public final String callingFeatureId; + public final Intent intent; + public final ResolveInfo rInfo; + public final ActivityInfo aInfo; + public final String resolvedType; + public final int callingPid; + public final int callingUid; + public final ActivityOptions checkedOptions; + + public ActivityInterceptorInfo(int realCallingUid, int realCallingPid, int userId, + String callingPackage, String callingFeatureId, Intent intent, + ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, int callingPid, + int callingUid, ActivityOptions checkedOptions) { + this.realCallingUid = realCallingUid; + this.realCallingPid = realCallingPid; + this.userId = userId; + this.callingPackage = callingPackage; + this.callingFeatureId = callingFeatureId; + this.intent = intent; + this.rInfo = rInfo; + this.aInfo = aInfo; + this.resolvedType = resolvedType; + this.callingPid = callingPid; + this.callingUid = callingUid; + this.checkedOptions = checkedOptions; + } + } +} diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index afe6345f096f..e640650d8f12 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -60,6 +60,8 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_T import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; import static com.android.server.am.MemoryStatUtil.MemoryStat; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_METRICS; @@ -91,6 +93,7 @@ import android.util.BoostFramework; import android.util.EventLog; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -159,6 +162,9 @@ class ActivityMetricsLogger { private final ArrayList<TransitionInfo> mTransitionInfoList = new ArrayList<>(); /** Map : Last launched activity => {@link TransitionInfo} */ private final ArrayMap<ActivityRecord, TransitionInfo> mLastTransitionInfo = new ArrayMap<>(); + /** SparseArray : Package UID => {@link PackageCompatStateInfo} */ + private final SparseArray<PackageCompatStateInfo> mPackageUidToCompatStateInfo = + new SparseArray<>(0); private ArtManagerInternal mArtManagerInternal; private final StringBuilder mStringBuilder = new StringBuilder(); @@ -190,7 +196,11 @@ class ActivityMetricsLogger { @VisibleForTesting boolean allDrawn() { - return mAssociatedTransitionInfo != null && mAssociatedTransitionInfo.allDrawn(); + return mAssociatedTransitionInfo != null && mAssociatedTransitionInfo.mIsDrawn; + } + + boolean hasActiveTransitionInfo() { + return mAssociatedTransitionInfo != null; } boolean contains(ActivityRecord r) { @@ -220,8 +230,8 @@ class ActivityMetricsLogger { final boolean mProcessRunning; /** whether the process of the launching activity didn't have any active activity. */ final boolean mProcessSwitch; - /** The activities that should be drawn. */ - final ArrayList<ActivityRecord> mPendingDrawActivities = new ArrayList<>(2); + /** Whether the last launched activity has reported drawn. */ + boolean mIsDrawn; /** The latest activity to have been launched. */ @NonNull ActivityRecord mLastLaunchedActivity; @@ -306,15 +316,15 @@ class ActivityMetricsLogger { return; } if (mLastLaunchedActivity != null) { - // Transfer the launch cookie because it is a consecutive launch event. + // Transfer the launch cookie and launch root task because it is a consecutive + // launch event. r.mLaunchCookie = mLastLaunchedActivity.mLaunchCookie; mLastLaunchedActivity.mLaunchCookie = null; + r.mLaunchRootTask = mLastLaunchedActivity.mLaunchRootTask; + mLastLaunchedActivity.mLaunchRootTask = null; } mLastLaunchedActivity = r; - if (!r.noDisplay && !r.isReportedDrawn()) { - if (DEBUG_METRICS) Slog.i(TAG, "Add pending draw " + r); - mPendingDrawActivities.add(r); - } + mIsDrawn = r.isReportedDrawn(); } /** Returns {@code true} if the incoming activity can belong to this transition. */ @@ -325,28 +335,7 @@ class ActivityMetricsLogger { /** @return {@code true} if the activity matches a launched activity in this transition. */ boolean contains(ActivityRecord r) { - return r != null && (r == mLastLaunchedActivity || mPendingDrawActivities.contains(r)); - } - - /** Called when the activity is drawn or won't be drawn. */ - void removePendingDrawActivity(ActivityRecord r) { - if (DEBUG_METRICS) Slog.i(TAG, "Remove pending draw " + r); - mPendingDrawActivities.remove(r); - } - - boolean allDrawn() { - return mPendingDrawActivities.isEmpty(); - } - - /** Only keep the records which can be drawn. */ - void updatePendingDraw() { - for (int i = mPendingDrawActivities.size() - 1; i >= 0; i--) { - final ActivityRecord r = mPendingDrawActivities.get(i); - if (!r.mVisibleRequested) { - if (DEBUG_METRICS) Slog.i(TAG, "Discard pending draw " + r); - mPendingDrawActivities.remove(i); - } - } + return r == mLastLaunchedActivity; } /** @@ -369,7 +358,7 @@ class ActivityMetricsLogger { @Override public String toString() { return "TransitionInfo{" + Integer.toHexString(System.identityHashCode(this)) - + " a=" + mLastLaunchedActivity + " ua=" + mPendingDrawActivities + "}"; + + " a=" + mLastLaunchedActivity + " d=" + mIsDrawn + "}"; } } @@ -450,6 +439,15 @@ class ActivityMetricsLogger { } } + /** Information about the App Compat state logging associated with a package UID . */ + private static final class PackageCompatStateInfo { + /** All activities that have a visible state. */ + final ArrayList<ActivityRecord> mVisibleActivities = new ArrayList<>(); + /** The last logged state. */ + int mLastLoggedState = APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; + @Nullable ActivityRecord mLastLoggedActivity; + } + ActivityMetricsLogger(ActivityTaskSupervisor supervisor, Looper looper) { mLastLogTimeSecs = SystemClock.elapsedRealtime() / 1000; mSupervisor = supervisor; @@ -636,6 +634,7 @@ class ActivityMetricsLogger { if (crossPackage) { startLaunchTrace(info); } + scheduleCheckActivityToBeDrawnIfSleeping(launchedActivity); return; } @@ -657,13 +656,7 @@ class ActivityMetricsLogger { // As abort for no process switch. launchObserverNotifyIntentFailed(); } - if (launchedActivity.mDisplayContent.isSleeping()) { - // It is unknown whether the activity can be drawn or not, e.g. it depends on the - // keyguard states and the attributes or flags set by the activity. If the activity - // keeps invisible in the grace period, the tracker will be cancelled so it won't get - // a very long launch time that takes unlocking as the end of launch. - scheduleCheckActivityToBeDrawn(launchedActivity, UNKNOWN_VISIBILITY_CHECK_DELAY_MS); - } + scheduleCheckActivityToBeDrawnIfSleeping(launchedActivity); // If the previous transitions are no longer visible, abort them to avoid counting the // launch time when resuming from back stack. E.g. launch 2 independent tasks in a short @@ -671,13 +664,22 @@ class ActivityMetricsLogger { // visible such as after the top task is finished. for (int i = mTransitionInfoList.size() - 2; i >= 0; i--) { final TransitionInfo prevInfo = mTransitionInfoList.get(i); - prevInfo.updatePendingDraw(); - if (prevInfo.allDrawn()) { + if (prevInfo.mIsDrawn || !prevInfo.mLastLaunchedActivity.mVisibleRequested) { abort(prevInfo, "nothing will be drawn"); } } } + private void scheduleCheckActivityToBeDrawnIfSleeping(@NonNull ActivityRecord r) { + if (r.mDisplayContent.isSleeping()) { + // It is unknown whether the activity can be drawn or not, e.g. it depends on the + // keyguard states and the attributes or flags set by the activity. If the activity + // keeps invisible in the grace period, the tracker will be cancelled so it won't get + // a very long launch time that takes unlocking as the end of launch. + scheduleCheckActivityToBeDrawn(r, UNKNOWN_VISIBILITY_CHECK_DELAY_MS); + } + } + /** * Notifies the tracker that all windows of the app have been drawn. * @@ -689,16 +691,16 @@ class ActivityMetricsLogger { if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn " + r); final TransitionInfo info = getActiveTransitionInfo(r); - if (info == null || info.allDrawn()) { - if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn no activity to be drawn"); + if (info == null || info.mIsDrawn) { + if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn not pending drawn " + info); return null; } // Always calculate the delay because the caller may need to know the individual drawn time. info.mWindowsDrawnDelayMs = info.calculateDelay(timestampNs); - info.removePendingDrawActivity(r); + info.mIsDrawn = true; final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info); - if (info.mLoggedTransitionStarting && info.allDrawn()) { - done(false /* abort */, info, "notifyWindowsDrawn - all windows drawn", timestampNs); + if (info.mLoggedTransitionStarting) { + done(false /* abort */, info, "notifyWindowsDrawn", timestampNs); } if (r.mWmService.isRecentsAnimationTarget(r)) { r.mWmService.getRecentsAnimationController().logRecentsAnimationStartTime( @@ -747,10 +749,8 @@ class ActivityMetricsLogger { info.mCurrentTransitionDelayMs = info.calculateDelay(timestampNs); info.mReason = activityToReason.valueAt(index); info.mLoggedTransitionStarting = true; - info.updatePendingDraw(); - if (info.allDrawn()) { - done(false /* abort */, info, "notifyTransitionStarting - all windows drawn", - timestampNs); + if (info.mIsDrawn) { + done(false /* abort */, info, "notifyTransitionStarting drawn", timestampNs); } } } @@ -765,6 +765,17 @@ class ActivityMetricsLogger { /** Makes sure that the reference to the removed activity is cleared. */ void notifyActivityRemoved(@NonNull ActivityRecord r) { mLastTransitionInfo.remove(r); + + final int packageUid = r.info.applicationInfo.uid; + final PackageCompatStateInfo compatStateInfo = mPackageUidToCompatStateInfo.get(packageUid); + if (compatStateInfo == null) { + return; + } + + compatStateInfo.mVisibleActivities.remove(r); + if (compatStateInfo.mLastLoggedActivity == r) { + compatStateInfo.mLastLoggedActivity = null; + } } /** @@ -781,19 +792,16 @@ class ActivityMetricsLogger { Slog.i(TAG, "notifyVisibilityChanged " + r + " visible=" + r.mVisibleRequested + " state=" + r.getState() + " finishing=" + r.finishing); } - if (r.isState(Task.ActivityState.RESUMED) && r.mDisplayContent.isSleeping()) { + if (r.isState(ActivityRecord.State.RESUMED) && r.mDisplayContent.isSleeping()) { // The activity may be launching while keyguard is locked. The keyguard may be dismissed // after the activity finished relayout, so skip the visibility check to avoid aborting // the tracking of launch event. return; } if (!r.mVisibleRequested || r.finishing) { - info.removePendingDrawActivity(r); - if (info.mLastLaunchedActivity == r) { - // Check if the tracker can be cancelled because the last launched activity may be - // no longer visible. - scheduleCheckActivityToBeDrawn(r, 0 /* delay */); - } + // Check if the tracker can be cancelled because the last launched activity may be + // no longer visible. + scheduleCheckActivityToBeDrawn(r, 0 /* delay */); } } @@ -812,17 +820,12 @@ class ActivityMetricsLogger { // If we have an active transition that's waiting on a certain activity that will be // invisible now, we'll never get onWindowsDrawn, so abort the transition if necessary. - // We have no active transitions. + // We have no active transitions. Or the notified activity whose visibility changed is + // no longer the launched activity, then we can still wait to get onWindowsDrawn. if (info == null) { return; } - // The notified activity whose visibility changed is no longer the launched activity. - // We can still wait to get onWindowsDrawn. - if (info.mLastLaunchedActivity != r) { - return; - } - // If the task of the launched activity contains any activity to be drawn, then the // window drawn event should report later to complete the transition. Otherwise all // activities in this task may be finished, invisible or drawn, so the transition event @@ -905,7 +908,6 @@ class ActivityMetricsLogger { } logAppTransitionFinished(info, isHibernating != null ? isHibernating : false); } - info.mPendingDrawActivities.clear(); mTransitionInfoList.remove(info); } @@ -1118,7 +1120,7 @@ class ActivityMetricsLogger { if (info == null) { return null; } - if (!info.allDrawn() && info.mPendingFullyDrawn == null) { + if (!info.mIsDrawn && info.mPendingFullyDrawn == null) { // There are still undrawn activities, postpone reporting fully drawn until all of its // windows are drawn. So that is closer to an usable state. info.mPendingFullyDrawn = () -> { @@ -1297,6 +1299,126 @@ class ActivityMetricsLogger { memoryStat.swapInBytes); } + /** + * Logs the current App Compat state of the given {@link ActivityRecord} with its package + * UID, if all of the following hold: + * <ul> + * <li>The current state is different than the last logged state for the package UID of the + * activity. + * <li>If the current state is NOT_VISIBLE, there is a previously logged state for the + * package UID and there are no other visible activities with the same package UID. + * <li>The last logged activity with the same package UID is either {@code activity} (or an + * activity that has been removed) or the last logged state is NOT_VISIBLE or NOT_LETTERBOXED. + * </ul> + * + * <p>If the current state is NOT_VISIBLE and the previous state which was logged by {@code + * activity} (or an activity that has been removed) wasn't, looks for the first visible activity + * with the same package UID that has a letterboxed state, or a non-letterboxed state if + * there isn't one, and logs that state. + * + * <p>This method assumes that the caller is wrapping the call with a synchronized block so + * that there won't be a race condition between two activities with the same package. + */ + void logAppCompatState(@NonNull ActivityRecord activity) { + final int packageUid = activity.info.applicationInfo.uid; + final int state = activity.getAppCompatState(); + + if (!mPackageUidToCompatStateInfo.contains(packageUid)) { + mPackageUidToCompatStateInfo.put(packageUid, new PackageCompatStateInfo()); + } + final PackageCompatStateInfo compatStateInfo = mPackageUidToCompatStateInfo.get(packageUid); + final int lastLoggedState = compatStateInfo.mLastLoggedState; + final ActivityRecord lastLoggedActivity = compatStateInfo.mLastLoggedActivity; + + final boolean isVisible = state != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; + final ArrayList<ActivityRecord> visibleActivities = compatStateInfo.mVisibleActivities; + if (isVisible && !visibleActivities.contains(activity)) { + visibleActivities.add(activity); + } else if (!isVisible) { + visibleActivities.remove(activity); + if (visibleActivities.isEmpty()) { + // No need to keep the entry if there are no visible activities. + mPackageUidToCompatStateInfo.remove(packageUid); + } + } + + if (state == lastLoggedState) { + // We don’t want to log the same state twice or log DEFAULT_NOT_VISIBLE before any + // visible state was logged. + return; + } + + if (!isVisible && !visibleActivities.isEmpty()) { + // There is another visible activity for this package UID. + if (lastLoggedActivity == null || activity == lastLoggedActivity) { + // Make sure a new visible state is logged if needed. + findAppCompatStateToLog(compatStateInfo, packageUid); + } + return; + } + + if (lastLoggedActivity != null && activity != lastLoggedActivity + && lastLoggedState != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE + && lastLoggedState != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED) { + // Another visible activity for this package UID has logged a letterboxed state. + return; + } + + logAppCompatStateInternal(activity, state, packageUid, compatStateInfo); + } + + /** + * Looks for the first visible activity in {@code compatStateInfo} that has a letterboxed + * state, or a non-letterboxed state if there isn't one, and logs that state for the given + * {@code packageUid}. + * + * <p>If there is a visible activity in {@code compatStateInfo} with the same state as the + * last logged state for the given {@code packageUid}, changes the last logged activity to + * reference the first such activity without actually logging the same state twice. + */ + private void findAppCompatStateToLog(PackageCompatStateInfo compatStateInfo, int packageUid) { + final ArrayList<ActivityRecord> visibleActivities = compatStateInfo.mVisibleActivities; + final int lastLoggedState = compatStateInfo.mLastLoggedState; + + ActivityRecord activityToLog = null; + int stateToLog = APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; + for (int i = 0; i < visibleActivities.size(); i++) { + ActivityRecord activity = visibleActivities.get(i); + int state = activity.getAppCompatState(); + if (state == lastLoggedState) { + // Change last logged activity without logging the same state twice. + compatStateInfo.mLastLoggedActivity = activity; + return; + } + if (state == APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) { + // This shouldn't happen. + Slog.w(TAG, "Visible activity with NOT_VISIBLE App Compat state for package UID: " + + packageUid); + continue; + } + if (stateToLog == APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE || ( + stateToLog == APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED + && state != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED)) { + activityToLog = activity; + stateToLog = state; + } + } + if (activityToLog != null && stateToLog != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) { + logAppCompatStateInternal(activityToLog, stateToLog, packageUid, compatStateInfo); + } + } + + private void logAppCompatStateInternal(@NonNull ActivityRecord activity, int state, + int packageUid, PackageCompatStateInfo compatStateInfo) { + compatStateInfo.mLastLoggedState = state; + compatStateInfo.mLastLoggedActivity = activity; + FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED, packageUid, state); + + if (DEBUG_METRICS) { + Slog.i(TAG, String.format("APP_COMPAT_STATE_CHANGED(%s, %s)", packageUid, state)); + } + } + private ArtManagerInternal getArtManagerInternal() { if (mArtManagerInternal == null) { // Note that this may be null. diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 319d97869461..7f3c733ec33e 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -46,8 +46,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.isSplitScreenWindowingMode; -import static android.app.servertransaction.TransferSplashScreenViewStateItem.ATTACH_TO; -import static android.app.servertransaction.TransferSplashScreenViewStateItem.HANDOVER_TO; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_HOME; import static android.content.Intent.CATEGORY_LAUNCHER; @@ -127,8 +125,24 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.wm.ActivityRecord.State.DESTROYED; +import static com.android.server.wm.ActivityRecord.State.DESTROYING; +import static com.android.server.wm.ActivityRecord.State.FINISHING; +import static com.android.server.wm.ActivityRecord.State.INITIALIZING; +import static com.android.server.wm.ActivityRecord.State.PAUSED; +import static com.android.server.wm.ActivityRecord.State.PAUSING; +import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS; +import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.ActivityRecord.State.STARTED; +import static com.android.server.wm.ActivityRecord.State.STOPPED; +import static com.android.server.wm.ActivityRecord.State.STOPPING; import static com.android.server.wm.ActivityRecordProto.ALL_DRAWN; import static com.android.server.wm.ActivityRecordProto.APP_STOPPED; import static com.android.server.wm.ActivityRecordProto.CLIENT_VISIBLE; @@ -140,11 +154,13 @@ import static com.android.server.wm.ActivityRecordProto.IS_ANIMATING; import static com.android.server.wm.ActivityRecordProto.IS_WAITING_FOR_TRANSITION_START; import static com.android.server.wm.ActivityRecordProto.LAST_ALL_DRAWN; import static com.android.server.wm.ActivityRecordProto.LAST_SURFACE_SHOWING; +import static com.android.server.wm.ActivityRecordProto.MIN_ASPECT_RATIO; import static com.android.server.wm.ActivityRecordProto.NAME; import static com.android.server.wm.ActivityRecordProto.NUM_DRAWN_WINDOWS; import static com.android.server.wm.ActivityRecordProto.NUM_INTERESTING_WINDOWS; import static com.android.server.wm.ActivityRecordProto.PIP_AUTO_ENTER_ENABLED; import static com.android.server.wm.ActivityRecordProto.PROC_ID; +import static com.android.server.wm.ActivityRecordProto.PROVIDES_MAX_BOUNDS; import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN; import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE; import static com.android.server.wm.ActivityRecordProto.STARTING_DISPLAYED; @@ -192,18 +208,7 @@ import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; -import static com.android.server.wm.Task.ActivityState.DESTROYED; -import static com.android.server.wm.Task.ActivityState.DESTROYING; -import static com.android.server.wm.Task.ActivityState.FINISHING; -import static com.android.server.wm.Task.ActivityState.INITIALIZING; -import static com.android.server.wm.Task.ActivityState.PAUSED; -import static com.android.server.wm.Task.ActivityState.PAUSING; -import static com.android.server.wm.Task.ActivityState.RESTARTING_PROCESS; -import static com.android.server.wm.Task.ActivityState.RESUMED; -import static com.android.server.wm.Task.ActivityState.STARTED; -import static com.android.server.wm.Task.ActivityState.STOPPED; -import static com.android.server.wm.Task.ActivityState.STOPPING; -import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE; +import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE; import static com.android.server.wm.TaskPersister.DEBUG; import static com.android.server.wm.TaskPersister.IMAGE_EXTENSION; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; @@ -219,7 +224,6 @@ import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY; import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN; -import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.END_TAG; @@ -259,6 +263,7 @@ import android.content.Intent; import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.ConstrainDisplayApisConfig; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -274,13 +279,13 @@ import android.os.Build; import android.os.Bundle; import android.os.Debug; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; -import android.os.storage.StorageManager; import android.service.contentcapture.ActivityEvent; import android.service.dreams.DreamActivity; import android.service.dreams.DreamManagerInternal; @@ -305,6 +310,7 @@ import android.view.InputApplicationHandle; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationTarget; +import android.view.Surface.Rotation; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type; @@ -312,24 +318,24 @@ import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.WindowManager.TransitionOldType; import android.view.animation.Animation; -import android.window.IRemoteTransition; +import android.window.RemoteTransition; import android.window.SizeConfigurationBuckets; import android.window.SplashScreen; import android.window.SplashScreenView; import android.window.SplashScreenView.SplashScreenViewParcelable; import android.window.TaskSnapshot; +import android.window.TransitionInfo.AnimationOptions; import android.window.WindowContainerToken; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ResolverActivity; import com.android.internal.content.ReferrerIntent; +import com.android.internal.os.TransferPipe; import com.android.internal.policy.AttributeCache; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ToBooleanFunction; import com.android.internal.util.XmlUtils; -import com.android.internal.util.function.pooled.PooledFunction; -import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.am.AppTimeTracker; import com.android.server.am.PendingIntentRecord; @@ -340,7 +346,6 @@ import com.android.server.uri.NeededUriGrants; import com.android.server.uri.UriPermissionOwner; import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot; import com.android.server.wm.SurfaceAnimator.AnimationType; -import com.android.server.wm.Task.ActivityState; import com.android.server.wm.WindowManagerService.H; import com.android.server.wm.utils.InsetUtils; @@ -349,6 +354,7 @@ import com.google.android.collect.Sets; import org.xmlpull.v1.XmlPullParserException; import java.io.File; +import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -402,10 +408,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe private static final int STARTING_WINDOW_TYPE_SNAPSHOT = 1; private static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2; - /** - * Value to increment the z-layer when boosting a layer during animations. BOOST in l33tsp34k. - */ - @VisibleForTesting static final int Z_BOOST_BASE = 800570000; static final int INVALID_PID = -1; // How long we wait until giving up on the last activity to pause. This @@ -491,13 +493,13 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe private ActivityOptions mPendingOptions; /** Non-null if {@link #mPendingOptions} specifies the remote animation. */ private RemoteAnimationAdapter mPendingRemoteAnimation; - private IRemoteTransition mPendingRemoteTransition; + private RemoteTransition mPendingRemoteTransition; ActivityOptions returningOptions; // options that are coming back via convertToTranslucent AppTimeTracker appTimeTracker; // set if we are tracking the time in this app/task/activity ActivityServiceConnectionsHolder mServiceConnectionsHolder; // Service connections. UriPermissionOwner uriPermissions; // current special URI access perms. WindowProcessController app; // if non-null, hosting application - private ActivityState mState; // current state we are in + private State mState; // current state we are in private Bundle mIcicle; // last saved activity state private PersistableBundle mPersistentState; // last persistently saved activity state private boolean mHaveState = true; // Indicates whether the last saved state of activity is @@ -549,11 +551,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe final ActivityTaskSupervisor mTaskSupervisor; final RootWindowContainer mRootWindowContainer; - static final int STARTING_WINDOW_NOT_SHOWN = 0; - static final int STARTING_WINDOW_SHOWN = 1; - static final int STARTING_WINDOW_REMOVED = 2; - int mStartingWindowState = STARTING_WINDOW_NOT_SHOWN; - // Tracking splash screen status from previous activity boolean mSplashScreenStyleEmpty = false; @@ -561,6 +558,21 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe static final int LAUNCH_SOURCE_TYPE_HOME = 2; static final int LAUNCH_SOURCE_TYPE_SYSTEMUI = 3; static final int LAUNCH_SOURCE_TYPE_APPLICATION = 4; + + enum State { + INITIALIZING, + STARTED, + RESUMED, + PAUSING, + PAUSED, + STOPPING, + STOPPED, + FINISHING, + DESTROYING, + DESTROYED, + RESTARTING_PROCESS + } + /** * The type of launch source. */ @@ -595,6 +607,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe */ private CompatDisplayInsets mCompatDisplayInsets; + private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig; + boolean pendingVoiceInteractionStart; // Waiting for activity-invoked voice session IVoiceInteractionSession voiceSession; // Voice interaction session for this activity @@ -660,6 +674,14 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe boolean allDrawn; private boolean mLastAllDrawn; + /** + * Solely for reporting to ActivityMetricsLogger. Just tracks whether, the last time this + * Actiivty was part of a syncset, all windows were ready by the time the sync was ready (vs. + * only the top-occluding ones). The assumption here is if some were not ready, they were + * covered with starting-window/splash-screen. + */ + boolean mLastAllReadyAtSync = false; + private boolean mLastContainsShowWhenLockedWindow; private boolean mLastContainsDismissKeyguardWindow; private boolean mLastContainsTurnScreenOnWindow; @@ -717,7 +739,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // TODO: rename to mNoDisplay @VisibleForTesting boolean noDisplay; - boolean mShowForAllUsers; + final boolean mShowForAllUsers; // TODO: Make this final int mTargetSdk; @@ -740,6 +762,13 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe boolean startingDisplayed; boolean startingMoved; + /** + * If it is non-null, it requires all activities who have the same starting data to be drawn + * to remove the starting window. + * TODO(b/189385912): Remove starting window related fields after migrating them to task. + */ + private StartingData mSharedStartingData; + boolean mHandleExitSplashScreen; @TransferSplashScreenState int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE; @@ -809,6 +838,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe */ private final Configuration mTmpConfig = new Configuration(); private final Rect mTmpBounds = new Rect(); + private final Rect mTmpOutNonDecorBounds = new Rect(); // Token for targeting this activity for assist purposes. final Binder assistToken = new Binder(); @@ -820,6 +850,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // Tracking cookie for the launch of this activity and it's task. IBinder mLaunchCookie; + // Tracking indicated launch root in order to propagate it among trampoline activities. + WindowContainerToken mLaunchRootTask; + // Entering PiP is usually done in two phases, we put the task into pinned mode first and // SystemUi sets the pinned mode on activity after transition is done. boolean mWaitForEnteringPinnedMode; @@ -874,19 +907,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } }; - private static String startingWindowStateToString(int state) { - switch (state) { - case STARTING_WINDOW_NOT_SHOWN: - return "STARTING_WINDOW_NOT_SHOWN"; - case STARTING_WINDOW_SHOWN: - return "STARTING_WINDOW_SHOWN"; - case STARTING_WINDOW_REMOVED: - return "STARTING_WINDOW_REMOVED"; - default: - return "unknown state=" + state; - } - } - @Override void dump(PrintWriter pw, String prefix, boolean dumpAll) { final long now = SystemClock.uptimeMillis(); @@ -1011,7 +1031,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe pw.println(mPendingRemoteAnimation.getCallingPid()); } if (mPendingRemoteTransition != null) { - pw.print(prefix + " pendingRemoteTransition=" + mPendingRemoteTransition); + pw.print(prefix + " pendingRemoteTransition=" + + mPendingRemoteTransition.getRemoteTransition()); } if (appTimeTracker != null) { appTimeTracker.dumpWithHeader(pw, prefix, false); @@ -1030,6 +1051,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe pw.print("launchCookie="); pw.println(mLaunchCookie); } + if (mLaunchRootTask != null) { + pw.print(prefix); + pw.print("mLaunchRootTask="); + pw.println(mLaunchRootTask); + } pw.print(prefix); pw.print("mHaveState="); pw.print(mHaveState); pw.print(" mIcicle="); pw.println(mIcicle); pw.print(prefix); pw.print("state="); pw.print(mState); @@ -1038,9 +1064,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe pw.print(" finishing="); pw.println(finishing); pw.print(prefix); pw.print("keysPaused="); pw.print(keysPaused); pw.print(" inHistory="); pw.print(inHistory); - pw.print(" idle="); pw.print(idle); - pw.print(" mStartingWindowState="); - pw.println(startingWindowStateToString(mStartingWindowState)); + pw.print(" idle="); pw.println(idle); pw.print(prefix); pw.print("occludesParent="); pw.print(occludesParent()); pw.print(" noDisplay="); pw.print(noDisplay); pw.print(" immersive="); pw.print(immersive); @@ -1049,6 +1073,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe pw.print(" forceNewConfig="); pw.println(forceNewConfig); pw.print(prefix); pw.print("mActivityType="); pw.println(activityTypeToString(getActivityType())); + pw.print(prefix); pw.print("mImeInsetsFrozenUntilStartInput="); + pw.println(mImeInsetsFrozenUntilStartInput); if (requestedVrComponent != null) { pw.print(prefix); pw.print("requestedVrComponent="); @@ -1085,6 +1111,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn); pw.print(" mIsExiting="); pw.println(mIsExiting); } + if (mSharedStartingData != null) { + pw.println(prefix + "mSharedStartingData=" + mSharedStartingData); + } if (mStartingWindow != null || mStartingSurface != null || startingDisplayed || startingMoved || mVisibleSetFromTransferredStartingWindow) { pw.print(prefix); pw.print("startingWindow="); pw.print(mStartingWindow); @@ -1135,10 +1164,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe if (info.getMaxAspectRatio() != 0) { pw.println(prefix + "maxAspectRatio=" + info.getMaxAspectRatio()); } - if (info.getMinAspectRatio() != 0) { - pw.println(prefix + "minAspectRatio=" + info.getMinAspectRatio()); + final float minAspectRatio = getMinAspectRatio(); + if (minAspectRatio != 0) { + pw.println(prefix + "minAspectRatio=" + minAspectRatio); } - if (info.getMinAspectRatio() != info.getManifestMinAspectRatio()) { + if (minAspectRatio != info.getManifestMinAspectRatio()) { // Log the fact that we've overridden the min aspect ratio from the manifest pw.println(prefix + "manifestMinAspectRatio=" + info.getManifestMinAspectRatio()); @@ -1148,8 +1178,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe if (info.configChanges != 0) { pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges)); } - pw.println(prefix + "neverSandboxDisplayApis=" + info.neverSandboxDisplayApis()); - pw.println(prefix + "alwaysSandboxDisplayApis=" + info.alwaysSandboxDisplayApis()); + pw.println(prefix + "neverSandboxDisplayApis=" + info.neverSandboxDisplayApis( + sConstrainDisplayApisConfig)); + pw.println(prefix + "alwaysSandboxDisplayApis=" + info.alwaysSandboxDisplayApis( + sConstrainDisplayApisConfig)); } if (mLastParentBeforePip != null) { pw.println(prefix + "lastParentTaskIdBeforePip=" + mLastParentBeforePip.mTaskId); @@ -1158,6 +1190,76 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe mLetterboxUiController.dump(pw, prefix); } + static boolean dumpActivity(FileDescriptor fd, PrintWriter pw, int index, ActivityRecord r, + String prefix, String label, boolean complete, boolean brief, boolean client, + String dumpPackage, boolean needNL, Runnable header, Task lastTask) { + if (dumpPackage != null && !dumpPackage.equals(r.packageName)) { + return false; + } + + final boolean full = !brief && (complete || !r.isInHistory()); + if (needNL) { + pw.println(""); + } + if (header != null) { + header.run(); + } + + String innerPrefix = prefix + " "; + String[] args = new String[0]; + if (lastTask != r.getTask()) { + lastTask = r.getTask(); + pw.print(prefix); + pw.print(full ? "* " : " "); + pw.println(lastTask); + if (full) { + lastTask.dump(pw, prefix + " "); + } else if (complete) { + // Complete + brief == give a summary. Isn't that obvious?!? + if (lastTask.intent != null) { + pw.print(prefix); + pw.print(" "); + pw.println(lastTask.intent.toInsecureString()); + } + } + } + pw.print(prefix); pw.print(full ? "* " : " "); pw.print(label); + pw.print(" #"); pw.print(index); pw.print(": "); + pw.println(r); + if (full) { + r.dump(pw, innerPrefix, true /* dumpAll */); + } else if (complete) { + // Complete + brief == give a summary. Isn't that obvious?!? + pw.print(innerPrefix); + pw.println(r.intent.toInsecureString()); + if (r.app != null) { + pw.print(innerPrefix); + pw.println(r.app); + } + } + if (client && r.attachedToProcess()) { + // flush anything that is already in the PrintWriter since the thread is going + // to write to the file descriptor directly + pw.flush(); + try { + TransferPipe tp = new TransferPipe(); + try { + r.app.getThread().dumpActivity( + tp.getWriteFd(), r.appToken, innerPrefix, args); + // Short timeout, since blocking here can deadlock with the application. + tp.go(fd, 2000); + } finally { + tp.kill(); + } + } catch (IOException e) { + pw.println(innerPrefix + "Failure while dumping the activity: " + e); + } catch (RemoteException e) { + pw.println(innerPrefix + "Got a RemoteException while dumping the activity"); + } + } + return true; + } + void setAppTimeTracker(AppTimeTracker att) { appTimeTracker = att; } @@ -1267,15 +1369,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe updatePictureInPictureMode(null, false); } else { mLastReportedMultiWindowMode = inMultiWindowMode; - computeConfigurationAfterMultiWindowModeChange(); - // If the activity is in stopping or stopped state, for instance, it's in the - // split screen task and not the top one, the last configuration it should keep - // is the one before multi-window mode change. - final ActivityState state = getState(); - if (state != STOPPED && state != STOPPING) { - ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS, - true /* ignoreVisibility */); - } + ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS, + false /* ignoreVisibility */); } } } @@ -1294,33 +1389,46 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // precede the configuration change from the resize. mLastReportedPictureInPictureMode = inPictureInPictureMode; mLastReportedMultiWindowMode = inPictureInPictureMode; - if (targetRootTaskBounds != null && !targetRootTaskBounds.isEmpty()) { - computeConfigurationAfterMultiWindowModeChange(); - } ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS, true /* ignoreVisibility */); } } - private void computeConfigurationAfterMultiWindowModeChange() { - final Configuration newConfig = new Configuration(); - newConfig.setTo(task.getRequestedOverrideConfiguration()); - Rect outBounds = newConfig.windowConfiguration.getBounds(); - final Configuration parentConfig = task.getParent().getConfiguration(); - task.adjustForMinimalTaskDimensions(outBounds, outBounds, parentConfig); - task.computeConfigResourceOverrides(newConfig, parentConfig); - } - Task getTask() { return task; } + @Nullable + TaskFragment getTaskFragment() { + WindowContainer parent = getParent(); + return parent != null ? parent.asTaskFragment() : null; + } + + /** Whether we should prepare a transition for this {@link ActivityRecord} parent change. */ + private boolean shouldStartChangeTransition( + @Nullable TaskFragment newParent, @Nullable TaskFragment oldParent) { + if (newParent == null || oldParent == null || !canStartChangeTransition()) { + return false; + } + + // Transition change for the activity moving into a TaskFragment of different bounds. + return newParent.isOrganizedTaskFragment() + && !newParent.getBounds().equals(oldParent.getBounds()); + } + @Override - void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) { - final Task oldTask = oldParent != null ? (Task) oldParent : null; - final Task newTask = newParent != null ? (Task) newParent : null; + void onParentChanged(ConfigurationContainer rawNewParent, ConfigurationContainer rawOldParent) { + final TaskFragment oldParent = (TaskFragment) rawOldParent; + final TaskFragment newParent = (TaskFragment) rawNewParent; + final Task oldTask = oldParent != null ? oldParent.getTask() : null; + final Task newTask = newParent != null ? newParent.getTask() : null; this.task = newTask; + if (shouldStartChangeTransition(newParent, oldParent)) { + // Animate change transition on TaskFragment level to get the correct window crop. + newParent.initializeChangeTransition(getBounds(), getSurfaceControl()); + } + super.onParentChanged(newParent, oldParent); if (isPersistable()) { @@ -1376,11 +1484,19 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe updateColorTransform(); - if (oldTask != null) { - oldTask.cleanUpActivityReferences(this); + if (oldParent != null) { + oldParent.cleanUpActivityReferences(this); } - if (newTask != null && isState(RESUMED)) { - newTask.setResumedActivity(this, "onParentChanged"); + + if (newParent != null && isState(RESUMED)) { + newParent.setResumedActivity(this, "onParentChanged"); + if (mStartingWindow != null && mStartingData != null + && mStartingData.mAssociatedTask == null && newParent.isEmbedded()) { + // The starting window should keep covering its task when the activity is + // reparented to a task fragment that may not fill the task bounds. + associateStartingDataWithTask(); + attachStartingSurfaceToAssociatedTask(); + } mImeInsetsFrozenUntilStartInput = false; } @@ -1429,7 +1545,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } // TODO(b/169035022): move to a more-appropriate place. - mAtmService.getTransitionController().collect(this); + mTransitionController.collect(this); if (prevDc.mOpeningApps.remove(this)) { // Transfer opening transition to new display. mDisplayContent.mOpeningApps.add(this); @@ -1674,6 +1790,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe info.windowLayout.windowLayoutAffinity = uid + ":" + info.windowLayout.windowLayoutAffinity; } + // Initialize once, when we know all system services are available. + if (sConstrainDisplayApisConfig == null) { + sConstrainDisplayApisConfig = new ConstrainDisplayApisConfig(); + } stateNotNeeded = (aInfo.flags & FLAG_STATE_NOT_NEEDED) != 0; nonLocalizedLabel = aInfo.nonLocalizedLabel; labelRes = aInfo.labelRes; @@ -1724,6 +1844,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null; mHandoverLaunchDisplayId = options.getLaunchDisplayId(); mLaunchCookie = options.getLaunchCookie(); + mLaunchRootTask = options.getLaunchRootTask(); } mPersistentState = persistentState; @@ -1808,6 +1929,13 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe task.setRootProcess(proc); } proc.addActivityIfNeeded(this); + + // Update the associated task fragment after setting the process, since it's required for + // filtering to only report activities that belong to the same process. + final TaskFragment tf = getTaskFragment(); + if (tf != null) { + tf.sendTaskFragmentInfoChanged(); + } } boolean hasProcess() { @@ -1938,9 +2066,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } } + @VisibleForTesting boolean addStartingWindow(String pkg, int resolvedTheme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags, - IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning, + ActivityRecord from, boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated, boolean useEmpty) { // If the display is frozen, we won't do anything until the actual window is // displayed so there is no reason to put in the starting window. @@ -1999,7 +2128,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } applyStartingWindowTheme(pkg, resolvedTheme); - if (transferStartingWindow(transferFrom)) { + if (from != null && transferStartingWindow(from)) { return true; } @@ -2024,6 +2153,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SnapshotStartingData"); mStartingData = new SnapshotStartingData(mWmService, snapshot, typeParams); + if (task.forAllLeafTaskFragments(TaskFragment::isEmbedded)) { + // Associate with the task so if this activity is resized by task fragment later, the + // starting window can keep the same bounds as the task. + associateStartingDataWithTask(); + } scheduleAddStartingWindow(); return true; } @@ -2217,7 +2351,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // unable to copy from shell, maybe it's not a splash screen. or something went wrong. // either way, abort and reset the sequence. if (parcelable == null - || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING) { + || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING + || mStartingWindow == null) { if (parcelable != null) { parcelable.clearIfNeeded(); } @@ -2226,13 +2361,17 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return; } // schedule attach splashScreen to client + final SurfaceControl windowAnimationLeash = TaskOrganizerController + .applyStartingWindowAnimation(mStartingWindow); try { mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT; mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, - TransferSplashScreenViewStateItem.obtain(ATTACH_TO, parcelable)); + TransferSplashScreenViewStateItem.obtain(parcelable, + windowAnimationLeash)); scheduleTransferSplashScreenTimeout(); } catch (Exception e) { Slog.w(TAG, "onCopySplashScreenComplete fail: " + this); + mStartingWindow.cancelAnimation(); parcelable.clearIfNeeded(); mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH; } @@ -2242,14 +2381,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe removeTransferSplashScreenTimeout(); // Client has draw the splash screen, so we can remove the starting window. if (mStartingWindow != null) { + mStartingWindow.cancelAnimation(); mStartingWindow.hide(false, false); } - try { - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, - TransferSplashScreenViewStateItem.obtain(HANDOVER_TO, null)); - } catch (Exception e) { - Slog.w(TAG, "onSplashScreenAttachComplete fail: " + this); - } // no matter what, remove the starting window. mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH; removeStartingWindowAnimation(false /* prepareAnimation */); @@ -2272,15 +2406,44 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } } - void removeStartingWindow() { - removeStartingWindowAnimation(true /* prepareAnimation */); + void attachStartingWindow(@NonNull WindowState startingWindow) { + startingWindow.mStartingData = mStartingData; + mStartingWindow = startingWindow; + if (mStartingData != null && mStartingData.mAssociatedTask != null) { + attachStartingSurfaceToAssociatedTask(); + } } - void removeStartingWindowAnimation(boolean prepareAnimation) { + private void attachStartingSurfaceToAssociatedTask() { + // Associate the configuration of starting window with the task. + overrideConfigurationPropagation(mStartingWindow, mStartingData.mAssociatedTask); + getSyncTransaction().reparent(mStartingWindow.mSurfaceControl, + mStartingData.mAssociatedTask.mSurfaceControl); + } + + private void associateStartingDataWithTask() { + mStartingData.mAssociatedTask = task; + task.forAllActivities(r -> { + if (r.mVisibleRequested && !r.firstWindowDrawn) { + r.mSharedStartingData = mStartingData; + } + }); + } + + void removeStartingWindow() { if (transferSplashScreenIfNeeded()) { return; } + removeStartingWindowAnimation(true /* prepareAnimation */); + } + + void removeStartingWindowAnimation(boolean prepareAnimation) { mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE; + if (mSharedStartingData != null) { + mSharedStartingData.mAssociatedTask.forAllActivities(r -> { + r.mSharedStartingData = null; + }); + } if (mStartingWindow == null) { if (mStartingData != null) { // Starting window has not been added yet, but it is scheduled to be added. @@ -2337,38 +2500,25 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } } - private void removeAppTokenFromDisplay() { - if (mWmService.mRoot == null) return; - - final DisplayContent dc = mWmService.mRoot.getDisplayContent(getDisplayId()); - if (dc == null) { - Slog.w(TAG, "removeAppTokenFromDisplay: Attempted to remove token: " - + appToken + " from non-existing displayId=" + getDisplayId()); - return; - } - // Resume key dispatching if it is currently paused before we remove the container. - resumeKeyDispatchingLocked(); - dc.removeAppToken(appToken.asBinder()); - } - /** - * Reparents this activity into {@param newTask} at the provided {@param position}. The caller - * should ensure that the {@param newTask} is not already the parent of this activity. + * Reparents this activity into {@param newTaskFrag} at the provided {@param position}. The + * caller should ensure that the {@param newTaskFrag} is not already the parent of this + * activity. */ - void reparent(Task newTask, int position, String reason) { + void reparent(TaskFragment newTaskFrag, int position, String reason) { if (getParent() == null) { Slog.w(TAG, "reparent: Attempted to reparent non-existing app token: " + appToken); return; } - final Task prevTask = task; - if (prevTask == newTask) { - throw new IllegalArgumentException(reason + ": task=" + newTask + final TaskFragment prevTaskFrag = getTaskFragment(); + if (prevTaskFrag == newTaskFrag) { + throw new IllegalArgumentException(reason + ": task fragment =" + newTaskFrag + " is already the parent of r=" + this); } ProtoLog.i(WM_DEBUG_ADD_REMOVE, "reparent: moving activity=%s" - + " to task=%d at %d", this, task.mTaskId, position); - reparent(newTask, position); + + " to new task fragment in task=%d at %d", this, task.mTaskId, position); + reparent(newTaskFrag, position); } private boolean isHomeIntent(Intent intent) { @@ -2489,6 +2639,19 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return task != null ? task.getOrganizedTask() : null; } + /** Returns the organized parent {@link TaskFragment}. */ + @Nullable + TaskFragment getOrganizedTaskFragment() { + final TaskFragment parent = getTaskFragment(); + return parent != null ? parent.getOrganizedTaskFragment() : null; + } + + @Override + boolean isEmbedded() { + final TaskFragment parent = getTaskFragment(); + return parent != null && parent.isEmbedded(); + } + @Override @Nullable TaskDisplayArea getDisplayArea() { @@ -2578,9 +2741,15 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } boolean isResizeable() { + return isResizeable(/* checkPictureInPictureSupport */ true); + } + + boolean isResizeable(boolean checkPictureInPictureSupport) { return mAtmService.mForceResizableActivities || ActivityInfo.isResizeableMode(info.resizeMode) - || info.supportsPictureInPicture(); + || (info.supportsPictureInPicture() && checkPictureInPictureSupport) + // If the activity can be embedded, it should inherit the bounds of task fragment. + || isEmbedded(); } /** @return whether this activity is non-resizeable but is forced to be resizable. */ @@ -2588,7 +2757,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe if (windowingMode == WINDOWING_MODE_PINNED && info.supportsPictureInPicture()) { return false; } - if (WindowConfiguration.inMultiWindowMode(windowingMode) && supportsMultiWindow() + // Activity should be resizable if the task is. + final boolean supportsMultiWindow = task != null + ? task.supportsMultiWindow() || supportsMultiWindow() + : supportsMultiWindow(); + if (WindowConfiguration.inMultiWindowMode(windowingMode) && supportsMultiWindow && !mAtmService.mForceResizableActivities) { // The non resizable app will be letterboxed instead of being forced resizable. return false; @@ -2665,7 +2838,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe final ActivityInfo.WindowLayout windowLayout = info.windowLayout; return windowLayout == null || tda.supportsActivityMinWidthHeightMultiWindow(windowLayout.minWidth, - windowLayout.minHeight); + windowLayout.minHeight, info); } /** @@ -2760,7 +2933,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe /** * @return Whether AppOps allows this package to enter picture-in-picture. */ - private boolean checkEnterPictureInPictureAppOpsState() { + boolean checkEnterPictureInPictureAppOpsState() { return mAtmService.getAppOpsManager().checkOpNoThrow( OP_PICTURE_IN_PICTURE, info.applicationInfo.uid, packageName) == MODE_ALLOWED; } @@ -2900,7 +3073,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe @interface FinishRequest {} /** - * See {@link #finishIfPossible(int, Intent, String, boolean)} + * See {@link #finishIfPossible(int, Intent, NeededUriGrants, String, boolean)} */ @FinishRequest int finishIfPossible(String reason, boolean oomAdj) { return finishIfPossible(Activity.RESULT_CANCELED, @@ -2934,7 +3107,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } final Task rootTask = getRootTask(); - final boolean mayAdjustTop = (isState(RESUMED) || rootTask.getResumedActivity() == null) + final boolean mayAdjustTop = (isState(RESUMED) || rootTask.getTopResumedActivity() == null) && rootTask.isFocusedRootTaskOnDisplay() // Do not adjust focus task because the task will be reused to launch new activity. && !task.isClearingToReuseTask(); @@ -2945,9 +3118,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe mAtmService.deferWindowLayout(); try { - final Transition newTransition = (!mAtmService.getTransitionController().isCollecting() - && mAtmService.getTransitionController().getTransitionPlayer() != null) - ? mAtmService.getTransitionController().createTransition(TRANSIT_CLOSE) : null; mTaskSupervisor.mNoHistoryActivities.remove(this); makeFinishingLocked(); // Make a local reference to its task since this.task could be set to null once this @@ -2979,10 +3149,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe final boolean endTask = task.getTopNonFinishingActivity() == null && !task.isClearingToReuseTask(); - if (newTransition != null) { - mAtmService.getTransitionController().requestStartTransition(newTransition, - endTask ? task : null, null /* remote */); - } + mTransitionController.requestCloseTransitionIfNeeded(endTask ? task : this); if (isState(RESUMED)) { if (endTask) { mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted( @@ -3013,12 +3180,12 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // Tell window manager to prepare for this one to be removed. setVisibility(false); - if (task.getPausingActivity() == null) { + if (getTaskFragment().getPausingActivity() == null) { ProtoLog.v(WM_DEBUG_STATES, "Finish needs to pause: %s", this); if (DEBUG_USER_LEAVING) { Slog.v(TAG_USER_LEAVING, "finish() => pause with userLeaving=false"); } - task.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, + getTaskFragment().startPausing(false /* userLeaving */, false /* uiSleeping */, null /* resuming */, "finish"); } @@ -3140,6 +3307,20 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // TODO(b/137329632): find the next activity directly underneath this one, not just anywhere final ActivityRecord next = getDisplayArea().topRunningActivity( true /* considerKeyguardState */); + + // If the finishing activity is the last activity of a organized TaskFragment and has an + // adjacent TaskFragment, check if the activity removal should be delayed. + boolean delayRemoval = false; + final TaskFragment taskFragment = getTaskFragment(); + if (next != null && taskFragment != null && taskFragment.isEmbedded()) { + final TaskFragment organized = taskFragment.getOrganizedTaskFragment(); + final TaskFragment adjacent = + organized != null ? organized.getAdjacentTaskFragment() : null; + if (adjacent != null && organized.topRunningActivity() == null) { + delayRemoval = organized.isDelayLastActivityRemoval(); + } + } + // isNextNotYetVisible is to check if the next activity is invisible, or it has been // requested to be invisible but its windows haven't reported as invisible. If so, it // implied that the current finishing activity should be added into stopping list rather @@ -3149,12 +3330,12 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // Clear last paused activity to ensure top activity can be resumed during sleeping. if (isNextNotYetVisible && mDisplayContent.isSleeping() - && next == next.getRootTask().mLastPausedActivity) { - next.getRootTask().mLastPausedActivity = null; + && next == next.getTaskFragment().mLastPausedActivity) { + next.getTaskFragment().clearLastPausedActivity(); } if (isCurrentVisible) { - if (isNextNotYetVisible) { + if (isNextNotYetVisible || delayRemoval) { // Add this activity to the list of stopping activities. It will be processed and // destroyed when the next activity reports idle. addToStopping(false /* scheduleIdle */, false /* idleDelayed */, @@ -3344,8 +3525,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe if (DEBUG_SWITCH) { final Task task = getTask(); Slog.v(TAG_SWITCH, "Safely destroying " + this + " in state " + getState() - + " resumed=" + task.getResumedActivity() - + " pausing=" + task.getPausingActivity() + + " resumed=" + task.getTopResumedActivity() + + " pausing=" + task.getTopPausingActivity() + " for reason " + reason); } return destroyImmediately(reason); @@ -3370,7 +3551,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe setState(DESTROYED, "removeFromHistory"); if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during remove for activity " + this); detachFromProcess(); - removeAppTokenFromDisplay(); + // Resume key dispatching if it is currently paused before we remove the container. + resumeKeyDispatchingLocked(); + mDisplayContent.removeAppToken(appToken); cleanUpActivityServices(); removeUriPermissionsLocked(); @@ -3388,10 +3571,18 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return; } finishing = true; + final TaskFragment taskFragment = getTaskFragment(); + if (taskFragment != null) { + final Task task = taskFragment.getTask(); + if (task != null && task.isClearingToReuseTask() + && taskFragment.getTopNonFinishingActivity() == null) { + taskFragment.mClearedTaskForReuse = true; + } + taskFragment.sendTaskFragmentInfoChanged(); + } if (stopped) { abortAndClearOptionsAnimation(); } - mAtmService.getTransitionController().requestTransitionIfNeeded(TRANSIT_CLOSE, this); } /** @@ -3425,7 +3616,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe * Note: Call before {@link #removeFromHistory(String)}. */ void cleanUp(boolean cleanServices, boolean setState) { - task.cleanUpActivityReferences(this); + getTaskFragment().cleanUpActivityReferences(this); clearLastParentBeforePip(); // Clean up the splash screen if it was still displayed. @@ -3486,6 +3677,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe if (mPendingRelaunchCount > 0) { mPendingRelaunchCount--; + if (mPendingRelaunchCount == 0 && !isClientVisible()) { + // Don't count if the client won't report drawn. + mRelaunchStartTime = 0; + } } else { // Update keyguard flags upon finishing relaunch. checkKeyguardFlagsChanged(); @@ -3534,7 +3729,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // failed more than twice. Skip activities that's already finishing cleanly by itself. remove = false; } else if ((!mHaveState && !stateNotNeeded - && !isState(ActivityState.RESTARTING_PROCESS)) || finishing) { + && !isState(State.RESTARTING_PROCESS)) || finishing) { // Don't currently have state for the activity, or it is finishing -- always remove it. remove = true; } else if (!mVisibleRequested && launchCount > 2 @@ -3570,20 +3765,36 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // to the restarted activity. nowVisible = mVisibleRequested; } + mTransitionController.requestCloseTransitionIfNeeded(this); cleanUp(true /* cleanServices */, true /* setState */); if (remove) { + if (mStartingData != null && mVisible && task != null) { + // A corner case that the app terminates its trampoline activity on a separated + // process by killing itself. Transfer the starting window to the next activity + // which will be visible, so the dead activity can be removed immediately (no + // longer animating) and the reveal animation can play normally on next activity. + final ActivityRecord top = task.topRunningActivity(); + if (top != null && !top.mVisible && top.shouldBeVisible()) { + top.transferStartingWindow(this); + } + } removeFromHistory("appDied"); } } @Override void removeImmediately() { - if (!finishing) { + if (mState != DESTROYED) { + Slog.w(TAG, "Force remove immediately " + this + " state=" + mState); // If Task#removeImmediately is called directly with alive activities, ensure that the // activities are destroyed and detached from process. destroyImmediately("removeImmediately"); + // Complete the destruction immediately because this activity will not be found in + // hierarchy, it is unable to report completion. + destroyed("removeImmediately"); + } else { + onRemovedFromDisplay(); } - onRemovedFromDisplay(); super.removeImmediately(); } @@ -3610,8 +3821,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Removing app token: %s", this); - commitVisibility(false /* visible */, true /* performLayout */); - getDisplayContent().mOpeningApps.remove(this); getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this); mWmService.mTaskSnapshotController.onAppRemoved(this); @@ -3619,8 +3828,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe mTaskSupervisor.mStoppingActivities.remove(this); waitingToShow = false; - // TODO(b/169035022): move to a more-appropriate place. - mAtmService.getTransitionController().collect(this); // Defer removal of this activity when either a child is animating, or app transition is on // going. App transition animation might be applied on the parent task not on the activity, // but the actual frame buffer is associated with the activity, so we have to keep the @@ -3632,10 +3839,20 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } else if (getDisplayContent().mAppTransition.isTransitionSet()) { getDisplayContent().mClosingApps.add(this); delayed = true; - } else if (mAtmService.getTransitionController().inTransition()) { + } else if (mTransitionController.inTransition()) { delayed = true; } + // Don't commit visibility if it is waiting to animate. It will be set post animation. + if (!delayed) { + commitVisibility(false /* visible */, true /* performLayout */); + } else { + setVisibleRequested(false /* visible */); + } + + // TODO(b/169035022): move to a more-appropriate place. + mTransitionController.collect(this); + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Removing app %s delayed=%b animation=%s animating=%b", this, delayed, getAnimation(), @@ -3797,17 +4014,14 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } } - boolean transferStartingWindow(IBinder transferFrom) { - final ActivityRecord fromActivity = getDisplayContent().getActivityRecord(transferFrom); - if (fromActivity == null) { - return false; - } - + private boolean transferStartingWindow(@NonNull ActivityRecord fromActivity) { final WindowState tStartingWindow = fromActivity.mStartingWindow; if (tStartingWindow != null && fromActivity.mStartingSurface != null) { // In this case, the starting icon has already been displayed, so start // letting windows get shown immediately without any more transitions. - getDisplayContent().mSkipAppTransitionAnimation = true; + if (fromActivity.mVisible) { + mDisplayContent.mSkipAppTransitionAnimation = true; + } ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Moving existing starting %s" + " from %s to %s", tStartingWindow, fromActivity, this); @@ -3823,6 +4037,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // Transfer the starting window over to the new token. mStartingData = fromActivity.mStartingData; + mSharedStartingData = fromActivity.mSharedStartingData; mStartingSurface = fromActivity.mStartingSurface; startingDisplayed = fromActivity.startingDisplayed; fromActivity.startingDisplayed = false; @@ -3837,7 +4052,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Removing starting %s from %s", tStartingWindow, fromActivity); - mAtmService.getTransitionController().collect(tStartingWindow); + mTransitionController.collect(tStartingWindow); tStartingWindow.reparent(this, POSITION_TOP); // Propagate other interesting state between the tokens. If the old token is displayed, @@ -3863,7 +4078,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // the token we transfer the animation over. Thus, set this flag to indicate // we've transferred the animation. mUseTransferredAnimation = true; - } else if (mAtmService.getTransitionController().getTransitionPlayer() != null) { + } else if (mTransitionController.getTransitionPlayer() != null) { // In the new transit system, just set this every time we transfer the window mUseTransferredAnimation = true; } @@ -3885,6 +4100,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Moving pending starting from %s to %s", fromActivity, this); mStartingData = fromActivity.mStartingData; + mSharedStartingData = fromActivity.mSharedStartingData; fromActivity.mStartingData = null; fromActivity.startingMoved = true; scheduleAddStartingWindow(); @@ -3903,16 +4119,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe * immediately finishes after, so we have to transfer T to M. */ void transferStartingWindowFromHiddenAboveTokenIfNeeded() { - final PooledFunction p = PooledLambda.obtainFunction(ActivityRecord::transferStartingWindow, - this, PooledLambda.__(ActivityRecord.class)); - task.forAllActivities(p); - p.recycle(); - } - - private boolean transferStartingWindow(ActivityRecord fromActivity) { - if (fromActivity == this) return true; - - return !fromActivity.mVisibleRequested && transferStartingWindow(fromActivity.token); + task.forAllActivities(fromActivity -> { + if (fromActivity == this) return true; + return !fromActivity.mVisibleRequested && transferStartingWindow(fromActivity); + }); } void checkKeyguardFlagsChanged() { @@ -3981,19 +4191,40 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe * conditions a) above. * Multi-windowing mode will be exited if {@code true} is returned. */ - boolean canShowWhenLocked() { - if (!inPinnedWindowingMode() && (mShowWhenLocked || containsShowWhenLockedWindow())) { + private static boolean canShowWhenLocked(ActivityRecord r) { + if (r == null || r.getTaskFragment() == null) { + return false; + } + if (!r.inPinnedWindowingMode() && (r.mShowWhenLocked || r.containsShowWhenLockedWindow())) { return true; - } else if (mInheritShownWhenLocked) { - final ActivityRecord r = task.getActivityBelow(this); - return r != null && !r.inPinnedWindowingMode() && (r.mShowWhenLocked - || r.containsShowWhenLockedWindow()); + } else if (r.mInheritShownWhenLocked) { + final ActivityRecord activity = r.getTaskFragment().getActivityBelow(r); + return activity != null && !activity.inPinnedWindowingMode() + && (activity.mShowWhenLocked || activity.containsShowWhenLockedWindow()); } else { return false; } } /** + * Determines if the activity can show while lock-screen is displayed. System displays + * activities while lock-screen is displayed only if all activities + * {@link #canShowWhenLocked(ActivityRecord)}. + * @see #canShowWhenLocked(ActivityRecord) + */ + boolean canShowWhenLocked() { + final TaskFragment taskFragment = getTaskFragment(); + if (taskFragment != null && taskFragment.getAdjacentTaskFragment() != null + && taskFragment.isEmbedded()) { + final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); + final ActivityRecord r = adjacentTaskFragment.getTopNonFinishingActivity(); + return canShowWhenLocked(this) && canShowWhenLocked(r); + } else { + return canShowWhenLocked(this); + } + } + + /** * @return Whether we are allowed to show non-starting windows at the moment. We disallow * showing windows during transitions in case we have windows that have wide-color-gamut * color mode set to avoid jank in the middle of the transition. @@ -4068,20 +4299,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return callback.test(this) ? this : null; } - @Override - protected void setLayer(Transaction t, int layer) { - if (!mSurfaceAnimator.hasLeash()) { - t.setLayer(mSurfaceControl, layer); - } - } - - @Override - protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) { - if (!mSurfaceAnimator.hasLeash()) { - t.setRelativeLayer(mSurfaceControl, relativeTo, layer); - } - } - void logStartActivity(int tag, Task task) { final Uri data = intent.getData(); final String strData = data != null ? data.toSafeString() : null; @@ -4258,6 +4475,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe private void applyOptionsAnimation(ActivityOptions pendingOptions, Intent intent) { final int animationType = pendingOptions.getAnimationType(); final DisplayContent displayContent = getDisplayContent(); + AnimationOptions options = null; + IRemoteCallback startCallback = null; + IRemoteCallback finishCallback = null; switch (animationType) { case ANIM_CUSTOM: displayContent.mAppTransition.overridePendingAppTransition( @@ -4267,11 +4487,19 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe pendingOptions.getAnimationStartedListener(), pendingOptions.getAnimationFinishedListener(), pendingOptions.getOverrideTaskTransition()); + options = AnimationOptions.makeCustomAnimOptions(pendingOptions.getPackageName(), + pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(), + pendingOptions.getOverrideTaskTransition()); + startCallback = pendingOptions.getAnimationStartedListener(); + finishCallback = pendingOptions.getAnimationFinishedListener(); break; case ANIM_CLIP_REVEAL: displayContent.mAppTransition.overridePendingAppTransitionClipReveal( pendingOptions.getStartX(), pendingOptions.getStartY(), pendingOptions.getWidth(), pendingOptions.getHeight()); + options = AnimationOptions.makeClipRevealAnimOptions( + pendingOptions.getStartX(), pendingOptions.getStartY(), + pendingOptions.getWidth(), pendingOptions.getHeight()); if (intent.getSourceBounds() == null) { intent.setSourceBounds(new Rect(pendingOptions.getStartX(), pendingOptions.getStartY(), @@ -4283,6 +4511,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe displayContent.mAppTransition.overridePendingAppTransitionScaleUp( pendingOptions.getStartX(), pendingOptions.getStartY(), pendingOptions.getWidth(), pendingOptions.getHeight()); + options = AnimationOptions.makeScaleUpAnimOptions( + pendingOptions.getStartX(), pendingOptions.getStartY(), + pendingOptions.getWidth(), pendingOptions.getHeight()); if (intent.getSourceBounds() == null) { intent.setSourceBounds(new Rect(pendingOptions.getStartX(), pendingOptions.getStartY(), @@ -4298,6 +4529,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe pendingOptions.getStartX(), pendingOptions.getStartY(), pendingOptions.getAnimationStartedListener(), scaleUp); + options = AnimationOptions.makeThumnbnailAnimOptions(buffer, + pendingOptions.getStartX(), pendingOptions.getStartY(), scaleUp); + startCallback = pendingOptions.getAnimationStartedListener(); if (intent.getSourceBounds() == null && buffer != null) { intent.setSourceBounds(new Rect(pendingOptions.getStartX(), pendingOptions.getStartY(), @@ -4337,6 +4571,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe case ANIM_OPEN_CROSS_PROFILE_APPS: displayContent.mAppTransition .overridePendingAppTransitionStartCrossProfileApps(); + options = AnimationOptions.makeCrossProfileAnimOptions(); break; case ANIM_NONE: case ANIM_UNDEFINED: @@ -4345,6 +4580,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe Slog.e(TAG_WM, "applyOptionsLocked: Unknown animationType=" + animationType); break; } + + if (options != null) { + mTransitionController.setOverrideAnimation(options, startCallback, finishCallback); + } } void clearAllDrawn() { @@ -4424,8 +4663,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return opts; } - IRemoteTransition takeRemoteTransition() { - IRemoteTransition out = mPendingRemoteTransition; + RemoteTransition takeRemoteTransition() { + RemoteTransition out = mPendingRemoteTransition; mPendingRemoteTransition = null; return out; } @@ -4477,6 +4716,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } } + boolean getDeferHidingClient() { + return mDeferHidingClient; + } + @Override boolean isVisible() { // If the activity isn't hidden then it is considered visible and there is no need to check @@ -4512,6 +4755,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe if (app != null) { mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */); } + logAppCompatState(); } /** @@ -4569,7 +4813,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe Debug.getCallers(6)); // Before setting mVisibleRequested so we can track changes. - mAtmService.getTransitionController().collect(this); + mTransitionController.collect(this); onChildVisibilityRequested(visible); @@ -4641,7 +4885,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } // If in a transition, defer commits for activities that are going invisible - if (!visible && mAtmService.getTransitionController().inTransition()) { + if (!visible && inTransition()) { return; } // If we are preparing an app transition, then delay changing @@ -4705,8 +4949,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe * @param visible {@code true} if this {@link ActivityRecord} should become visible, otherwise * this should become invisible. * @param performLayout if {@code true}, perform surface placement after committing visibility. + * @param fromTransition {@code true} if this is part of finishing a transition. */ - void commitVisibility(boolean visible, boolean performLayout) { + void commitVisibility(boolean visible, boolean performLayout, boolean fromTransition) { // Reset the state of mVisibleSetFromTransferredStartingWindow since visibility is actually // been set by the app now. mVisibleSetFromTransferredStartingWindow = false; @@ -4726,7 +4971,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } else { // If we are being set visible, and the starting window is not yet displayed, // then make sure it doesn't get displayed. - if (mStartingWindow != null && !mStartingWindow.isDrawn()) { + if (mStartingWindow != null && !mStartingWindow.isDrawn() + && (firstWindowDrawn || allDrawn)) { mStartingWindow.clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY); mStartingWindow.mLegacyPolicyVisibilityAfterAnim = false; } @@ -4734,6 +4980,14 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // getting visible so we also wait for them. forAllWindows(mWmService::makeWindowFreezingScreenIfNeededLocked, true); } + // dispatchTaskInfoChangedIfNeeded() right after ActivityRecord#setVisibility() can report + // the stale visible state, because the state will be updated after the app transition. + // So tries to report the actual visible state again where the state is changed. + Task task = getOrganizedTask(); + while (task != null) { + task.dispatchTaskInfoChangedIfNeeded(false /* force */); + task = task.getParent().asTask(); + } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "commitVisibility: %s: visible=%b mVisibleRequested=%b", this, isVisible(), mVisibleRequested); @@ -4747,7 +5001,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/); mUseTransferredAnimation = false; - postApplyAnimation(visible); + postApplyAnimation(visible, fromTransition); + } + + void commitVisibility(boolean visible, boolean performLayout) { + commitVisibility(visible, performLayout, false /* fromTransition */); } /** @@ -4758,12 +5016,16 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe * * @param visible {@code true} if this {@link ActivityRecord} has become visible, otherwise * this has become invisible. + * @param fromTransition {@code true} if this call is part of finishing a transition. This is + * needed because the shell transition is no-longer active by the time + * commitVisibility is called. */ - private void postApplyAnimation(boolean visible) { + private void postApplyAnimation(boolean visible, boolean fromTransition) { + final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled(); final boolean delayed = isAnimating(PARENTS | CHILDREN, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION | ANIMATION_TYPE_RECENTS); - if (!delayed) { + if (!delayed && !usingShellTransitions) { // We aren't delayed anything, but exiting windows rely on the animation finished // callback being called in case the ActivityRecord was pretending to be delayed, // which we might have done because we were in closing/opening apps list. @@ -4782,8 +5044,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // updated. // If we're becoming invisible, update the client visibility if we are not running an // animation. Otherwise, we'll update client visibility in onAnimationFinished. - if (visible || !isAnimating(PARENTS, - ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) { + if (visible || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS) + || usingShellTransitions) { setClientVisible(visible); } @@ -4799,7 +5061,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe final DisplayContent displayContent = getDisplayContent(); if (!displayContent.mClosingApps.contains(this) - && !displayContent.mOpeningApps.contains(this)) { + && !displayContent.mOpeningApps.contains(this) + && !fromTransition) { // Take the screenshot before possibly hiding the WSA, otherwise the screenshot // will not be taken. mWmService.mTaskSnapshotController.notifyAppVisibilityChanged(this, visible); @@ -4893,7 +5156,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return mCurrentLaunchCanTurnScreenOn; } - void setState(ActivityState state, String reason) { + void setState(State state, String reason) { ProtoLog.v(WM_DEBUG_STATES, "State movement: %s from:%s to:%s reason:%s", this, getState(), state, reason); @@ -4907,8 +5170,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe callServiceTrackeronActivityStatechange(state, false); - if (task != null) { - task.onActivityStateChanged(this, state, reason); + if (getTaskFragment() != null) { + getTaskFragment().onActivityStateChanged(this, state, reason); } // The WindowManager interprets the app stopping signal as @@ -4968,7 +5231,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } } - void callServiceTrackeronActivityStatechange(ActivityState state, boolean early_notify) { + void callServiceTrackeronActivityStatechange(State state, boolean early_notify) { IServicetracker mServicetracker; ActivityDetails aDetails = new ActivityDetails(); ActivityStats aStats = new ActivityStats(); @@ -5038,44 +5301,42 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } - ActivityState getState() { + State getState() { return mState; } /** * Returns {@code true} if the Activity is in the specified state. */ - boolean isState(ActivityState state) { + boolean isState(State state) { return state == mState; } /** * Returns {@code true} if the Activity is in one of the specified states. */ - boolean isState(ActivityState state1, ActivityState state2) { + boolean isState(State state1, State state2) { return state1 == mState || state2 == mState; } /** * Returns {@code true} if the Activity is in one of the specified states. */ - boolean isState(ActivityState state1, ActivityState state2, ActivityState state3) { + boolean isState(State state1, State state2, State state3) { return state1 == mState || state2 == mState || state3 == mState; } /** * Returns {@code true} if the Activity is in one of the specified states. */ - boolean isState(ActivityState state1, ActivityState state2, ActivityState state3, - ActivityState state4) { + boolean isState(State state1, State state2, State state3, State state4) { return state1 == mState || state2 == mState || state3 == mState || state4 == mState; } /** * Returns {@code true} if the Activity is in one of the specified states. */ - boolean isState(ActivityState state1, ActivityState state2, ActivityState state3, - ActivityState state4, ActivityState state5) { + boolean isState(State state1, State state2, State state3, State state4, State state5) { return state1 == mState || state2 == mState || state3 == mState || state4 == mState || state5 == mState; } @@ -5083,8 +5344,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe /** * Returns {@code true} if the Activity is in one of the specified states. */ - boolean isState(ActivityState state1, ActivityState state2, ActivityState state3, - ActivityState state4, ActivityState state5, ActivityState state6) { + boolean isState(State state1, State state2, State state3, State state4, State state5, + State state6) { return state1 == mState || state2 == mState || state3 == mState || state4 == mState || state5 == mState || state6 == mState; } @@ -5141,6 +5402,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe void notifyAppStopped() { ProtoLog.v(WM_DEBUG_ADD_REMOVE, "notifyAppStopped: %s", this); mAppStopped = true; + firstWindowDrawn = false; // This is to fix the edge case that auto-enter-pip is finished in Launcher but app calls // setAutoEnterEnabled(false) and transitions to STOPPED state, see b/191930787. // Clear any surface transactions and content overlay in this case. @@ -5212,7 +5474,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe void updateVisibilityIgnoringKeyguard(boolean behindFullscreenActivity) { visibleIgnoringKeyguard = (!behindFullscreenActivity || mLaunchTaskBehind) - && okToShowLocked(); + && showToCurrentUser(); } boolean shouldBeVisible() { @@ -5282,7 +5544,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // returns. Just need to confirm this reasoning makes sense. final boolean deferHidingClient = canEnterPictureInPicture && !isState(STARTED, STOPPING, STOPPED, PAUSED); - if (deferHidingClient && pictureInPictureArgs.isAutoEnterEnabled()) { + if (!mTransitionController.isShellTransitionsEnabled() + && deferHidingClient && pictureInPictureArgs.isAutoEnterEnabled()) { // Go ahead and just put the activity in pip if it supports auto-pip. mAtmService.enterPictureInPictureMode(this, pictureInPictureArgs); return; @@ -5298,13 +5561,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe supportsEnterPipOnTaskSwitch = false; break; case RESUMED: - // If the app is capable of entering PIP, we should try pausing it now - // so it can PIP correctly. - if (deferHidingClient) { - task.startPausingLocked(false /* uiSleeping */, - null /* resuming */, "makeInvisible"); - break; - } case INITIALIZING: case PAUSING: case PAUSED: @@ -5403,7 +5659,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe */ private boolean shouldBeResumed(ActivityRecord activeActivity) { return shouldMakeActive(activeActivity) && isFocusable() - && getTask().getVisibility(activeActivity) == TASK_VISIBILITY_VISIBLE + && getTaskFragment().getVisibility(activeActivity) + == TASK_FRAGMENT_VISIBILITY_VISIBLE && canResumeByCompat(); } @@ -5457,7 +5714,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe if (!task.hasChild(this)) { throw new IllegalStateException("Activity not found in its task"); } - return task.topRunningActivity() == this; + return getTaskFragment().topRunningActivity() == this; } void handleAlreadyVisible() { @@ -5551,16 +5808,17 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe ProtoLog.v(WM_DEBUG_STATES, "Activity paused: token=%s, timeout=%b", appToken, timeout); - if (task != null) { + final TaskFragment taskFragment = getTaskFragment(); + if (taskFragment != null) { removePauseTimeout(); - final ActivityRecord pausingActivity = task.getPausingActivity(); + final ActivityRecord pausingActivity = taskFragment.getPausingActivity(); if (pausingActivity == this) { ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSED: %s %s", this, (timeout ? "(due to timeout)" : " (pause complete)")); mAtmService.deferWindowLayout(); try { - task.completePauseLocked(true /* resumeNext */, null /* resumingActivity */); + taskFragment.completePause(true /* resumeNext */, null /* resumingActivity */); } finally { mAtmService.continueWindowLayout(); } @@ -5821,6 +6079,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } void startFreezingScreen(int overrideOriginalDisplayRotation) { + if (mTransitionController.isShellTransitionsEnabled()) { + return; + } ProtoLog.i(WM_DEBUG_ORIENTATION, "Set freezing of %s: visible=%b freezing=%b visibleRequested=%b. %s", appToken, isVisible(), mFreezingScreen, mVisibleRequested, @@ -5921,7 +6182,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } } - void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) { + void onFirstWindowDrawn(WindowState win) { firstWindowDrawn = true; // stop tracking mSplashScreenStyleEmpty = true; @@ -5937,7 +6198,22 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // own stuff. win.cancelAnimation(); } - removeStartingWindow(); + + // Remove starting window directly if is in a pure task. Otherwise if it is associated with + // a task (e.g. nested task fragment), then remove only if all visible windows in the task + // are drawn. + final Task associatedTask = + mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null; + if (associatedTask == null) { + removeStartingWindow(); + } else if (associatedTask.getActivity( + r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) { + // The last drawn activity may not be the one that owns the starting window. + final ActivityRecord r = associatedTask.topActivityContainsStartingWindow(); + if (r != null) { + r.removeStartingWindow(); + } + } updateReportedVisibilityLocked(); } @@ -5981,6 +6257,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe if (task != null) { task.setHasBeenVisible(true); } + // Clear indicated launch root task because there's no trampoline activity to expect after + // the windows are drawn. + mLaunchRootTask = null; } /** Called when the windows associated app window container are visible. */ @@ -6116,11 +6395,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // TODO(shell-transitions): Remove mDeferHidingClient once everything is shell-transitions. // pip activities should just remain in clientVisible. if (!clientVisible && mDeferHidingClient) return; - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, - "setClientVisible: %s clientVisible=%b Callers=%s", this, clientVisible, - Debug.getCallers(5)); super.setClientVisible(clientVisible); - sendAppVisibilityToClients(); } /** @@ -6239,9 +6514,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return this; } // Try to use the one which is closest to top. - ActivityRecord r = rootTask.getResumedActivity(); + ActivityRecord r = rootTask.getTopResumedActivity(); if (r == null) { - r = rootTask.getPausingActivity(); + r = rootTask.getTopPausingActivity(); } if (r != null) { return r; @@ -6250,22 +6525,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return this; } - /** Checks whether the activity should be shown for current user. */ - public boolean okToShowLocked() { - // We cannot show activities when the device is locked and the application is not - // encryption aware. - if (!StorageManager.isUserKeyUnlocked(mUserId) - && !info.applicationInfo.isEncryptionAware()) { - return false; - } - - return (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0 - || (mTaskSupervisor.isCurrentProfileLocked(mUserId) - && mAtmService.mAmInternal.isUserRunning(mUserId, 0 /* flags */)); - } - boolean canBeTopRunning() { - return !finishing && okToShowLocked(); + return !finishing && showToCurrentUser(); } /** @@ -6302,6 +6563,12 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return null; } + @Nullable + static ActivityRecord isInAnyTask(IBinder token) { + final ActivityRecord r = ActivityRecord.forTokenLocked(token); + return (r != null && r.isAttached()) ? r : null; + } + /** * @return display id to which this record is attached, * {@link android.view.Display#INVALID_DISPLAY} if not attached. @@ -6319,7 +6586,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // This would be redundant. return false; } - if (isState(RESUMED) || getRootTask() == null || this == task.getPausingActivity() + if (isState(RESUMED) || getRootTask() == null + || this == getTaskFragment().getPausingActivity() || !mHaveState || !stopped) { // We're not ready for this kind of thing. return false; @@ -6426,6 +6694,13 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON) { return false; } + // Choose the default behavior for Launcher and SystemUI when the SplashScreen style is + // not specified in the ActivityOptions. + if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME) { + return false; + } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) { + return true; + } } if (sourceRecord == null) { sourceRecord = searchCandidateLaunchingActivity(); @@ -6435,14 +6710,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return sourceRecord.mSplashScreenStyleEmpty; } - // If this activity was launched from a system surface, never use an empty splash screen + // If this activity was launched from Launcher or System for first start, never use an + // empty splash screen. // Need to check sourceRecord before in case this activity is launched from service. - if (launchedFromSystemSurface()) { - return false; - } - - // Otherwise use empty. - return true; + return !startActivity || !(mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEM + || mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME); } private int getSplashscreenTheme() { @@ -6506,13 +6778,12 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe final boolean newSingleActivity = !newTask && !activityCreated && task.getActivity((r) -> !r.finishing && r != this) == null; - final boolean shown = addStartingWindow(packageName, resolvedTheme, + final boolean scheduled = addStartingWindow(packageName, resolvedTheme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags, - prev != null ? prev.appToken : null, - newTask || newSingleActivity, taskSwitch, isProcessRunning(), + prev, newTask || newSingleActivity, taskSwitch, isProcessRunning(), allowTaskSnapshot(), activityCreated, mSplashScreenStyleEmpty); - if (shown) { - mStartingWindowState = STARTING_WINDOW_SHOWN; + if (DEBUG_STARTING_WINDOW_VERBOSE && scheduled) { + Slog.d(TAG, "Scheduled starting window for " + this); } } @@ -6524,14 +6795,12 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe * It should only be called if this activity is behind other fullscreen activity. */ void cancelInitializing() { - if (mStartingWindowState == STARTING_WINDOW_SHOWN) { + if (mStartingData != null) { // Remove orphaned starting window. if (DEBUG_VISIBILITY) Slog.w(TAG_VISIBILITY, "Found orphaned starting window " + this); - mStartingWindowState = STARTING_WINDOW_REMOVED; removeStartingWindowAnimation(false /* prepareAnimation */); } - if (isState(INITIALIZING) && !shouldBeVisible( - true /* behindFullscreenActivity */, true /* ignoringKeyguard */)) { + if (!mDisplayContent.mUnknownAppVisibilityController.allResolved()) { // Remove the unknown visibility record because an invisible activity shouldn't block // the keyguard transition. mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this); @@ -6576,17 +6845,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } } - boolean hasWindowsAlive() { - for (int i = mChildren.size() - 1; i >= 0; i--) { - // No need to loop through child windows as the answer should be the same as that of the - // parent window. - if (!(mChildren.get(i)).mAppDied) { - return true; - } - } - return false; - } - void setWillReplaceWindows(boolean animate) { ProtoLog.d(WM_DEBUG_ADD_REMOVE, "Marking app token %s with replacing windows.", this); @@ -6670,12 +6928,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return candidate; } - SurfaceControl getAppAnimationLayer() { - return getAppAnimationLayer(isActivityTypeHome() ? ANIMATION_LAYER_HOME - : needsZBoost() ? ANIMATION_LAYER_BOOSTED - : ANIMATION_LAYER_STANDARD); - } - @Override boolean needsZBoost() { return mNeedsZBoost || super.needsZBoost(); @@ -6745,29 +6997,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe || mDisplayContent.isNextTransitionForward(); } - private int getAnimationLayer() { - // The leash is parented to the animation layer. We need to preserve the z-order by using - // the prefix order index, but we boost if necessary. - int layer; - if (!inPinnedWindowingMode()) { - layer = getPrefixOrderIndex(); - } else { - // Root pinned tasks have animations take place within themselves rather than an - // animation layer so we need to preserve the order relative to the root task (e.g. - // the order of our task/parent). - layer = getParent().getPrefixOrderIndex(); - } - - if (mNeedsZBoost) { - layer += Z_BOOST_BASE; - } - return layer; - } - @Override - public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { - t.setLayer(leash, getAnimationLayer()); - getDisplayContent().assignRootTaskOrdering(); + void resetSurfacePositionForAnimationLeash(SurfaceControl.Transaction t) { + // Noop as Activity may be offset for letterbox } @Override @@ -6796,7 +7028,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // Crop to root task bounds. t.setLayer(leash, 0); - t.setLayer(mAnimationBoundsLayer, getAnimationLayer()); + t.setLayer(mAnimationBoundsLayer, getLastLayer()); // Reparent leash to animation bounds layer. t.reparent(leash, mAnimationBoundsLayer); @@ -6889,7 +7121,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe Rect appRect; if (win != null) { insets = win.getInsetsStateWithVisibilityOverride().calculateInsets( - win.getFrame(), Type.systemBars(), false /* ignoreVisibility */); + win.getFrame(), Type.systemBars(), false /* ignoreVisibility */).toRect(); appRect = new Rect(win.getFrame()); appRect.inset(insets); } else { @@ -6898,8 +7130,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } final Configuration displayConfig = mDisplayContent.getConfiguration(); return getDisplayContent().mAppTransition.createThumbnailAspectScaleAnimationLocked( - appRect, insets, thumbnailHeader, task, displayConfig.uiMode, - displayConfig.orientation); + appRect, insets, thumbnailHeader, task, displayConfig.orientation); } @Override @@ -7046,7 +7277,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return; } - mDisplayContent.mPinnedTaskController.onCancelFixedRotationTransform(task); + mDisplayContent.mPinnedTaskController.onCancelFixedRotationTransform(); // Perform rotation animation according to the rotation of this activity. startFreezingScreen(originalDisplayRotation); // This activity may relaunch or perform configuration change so once it has reported drawn, @@ -7202,7 +7433,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return false; } } - return !isResizeable() && (info.isFixedOrientation() || info.hasFixedAspectRatio()) + // Activity should be resizable if the task is. + final boolean isResizeable = task != null + ? task.isResizeable() || isResizeable() + : isResizeable(); + return !isResizeable && (info.isFixedOrientation() || hasFixedAspectRatio()) // The configuration of non-standard type should be enforced by system. // {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} is set when this activity is // added to a task, but this function is called when resolving the launch params, at @@ -7324,7 +7559,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // If the activity has requested override bounds, the configuration needs to be // computed accordingly. if (!matchParentBounds()) { - task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); + getTaskFragment().computeConfigResourceOverrides(resolvedConfig, + newParentConfiguration); } // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds // are already calculated in resolveFixedOrientationConfiguration. @@ -7367,14 +7603,16 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe + "should create compatDisplayInsets = %s", getUid(), mTmpBounds, - info.neverSandboxDisplayApis(), - info.alwaysSandboxDisplayApis(), + info.neverSandboxDisplayApis(sConstrainDisplayApisConfig), + info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig), !matchParentBounds(), mCompatDisplayInsets != null, shouldCreateCompatDisplayInsets()); } resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); } + + logAppCompatState(); } /** @@ -7384,24 +7622,61 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe * LetterboxUiController#shouldShowLetterboxUi} for more context. */ boolean areBoundsLetterboxed() { + return getAppCompatState(/* ignoreVisibility= */ true) + != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED; + } + + /** + * Logs the current App Compat state via {@link ActivityMetricsLogger#logAppCompatState}. + */ + private void logAppCompatState() { + mTaskSupervisor.getActivityMetricsLogger().logAppCompatState(this); + } + + /** + * Returns the current App Compat state of this activity. + * + * <p>The App Compat state indicates whether the activity is visible and letterboxed, and if so + * what is the reason for letterboxing. The state is used for logging the time spent in + * letterbox (sliced by the reason) vs non-letterbox per app. + */ + int getAppCompatState() { + return getAppCompatState(/* ignoreVisibility= */ false); + } + + /** + * Same as {@link #getAppCompatState()} except when {@code ignoreVisibility} the visibility + * of the activity is ignored. + * + * @param ignoreVisibility whether to ignore the visibility of the activity and not return + * NOT_VISIBLE if {@code mVisibleRequested} is false. + */ + private int getAppCompatState(boolean ignoreVisibility) { + if (!ignoreVisibility && !mVisibleRequested) { + return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; + } if (mInSizeCompatModeForBounds) { - return true; + return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; } // Letterbox for fixed orientation. This check returns true only when an activity is // letterboxed for fixed orientation. Aspect ratio restrictions are also applied if // present. But this doesn't return true when the activity is letterboxed only because // of aspect ratio restrictions. if (isLetterboxedForFixedOrientationAndAspectRatio()) { - return true; + return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION; } // Letterbox for limited aspect ratio. - return mIsAspectRatioApplied; + if (mIsAspectRatioApplied) { + return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO; + } + + return APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED; } /** * Adjusts horizontal position of resolved bounds if they doesn't fill the parent using gravity * requested in the config or via an ADB command. For more context see {@link - * WindowManagerService#getLetterboxHorizontalPositionMultiplier}. + * LetterboxUiController#getHorizontalPositionMultiplier(Configuration)}. */ private void updateResolvedBoundsHorizontalPosition(Configuration newParentConfiguration) { final Configuration resolvedConfig = getResolvedOverrideConfiguration(); @@ -7421,11 +7696,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe parentAppBounds.width(), screenResolvedBounds.width()); } else { float positionMultiplier = - mWmService.mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(); - positionMultiplier = - (positionMultiplier < 0.0f || positionMultiplier > 1.0f) - // Default to central position if invalid value is provided. - ? 0.5f : positionMultiplier; + mLetterboxUiController.getHorizontalPositionMultiplier(newParentConfiguration); offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width()) * positionMultiplier); } @@ -7439,7 +7710,16 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } // Since bounds has changed, the configuration needs to be computed accordingly. - task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); + getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); + } + + void recomputeConfiguration() { + onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); + } + + boolean isInTransition() { + return mTransitionController.inTransition() // Shell transitions. + || isAnimating(PARENTS | TRANSITION); // Legacy transitions. } /** @@ -7455,8 +7735,60 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } /** - * Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation - * change and the requested orientation is different from the parent. + * In some cases, applying insets to bounds changes the orientation. For example, if a + * close-to-square display rotates to portrait to respect a portrait orientation activity, after + * insets such as the status and nav bars are applied, the activity may actually have a + * landscape orientation. This method checks whether the orientations of the activity window + * with and without insets match or if the orientation with insets already matches the + * requested orientation. If not, it may be necessary to letterbox the window. + * @param parentBounds are the new parent bounds passed down to the activity and should be used + * to compute the stable bounds. + * @param outStableBounds will store the stable bounds, which are the bounds with insets + * applied, if orientation is not respected when insets are applied. + * Otherwise outStableBounds will be empty. Stable bounds should be used + * to compute letterboxed bounds if orientation is not respected when + * insets are applied. + */ + private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds) { + outStableBounds.setEmpty(); + if (mDisplayContent == null) { + return true; + } + // Only need to make changes if activity sets an orientation + final int requestedOrientation = getRequestedConfigurationOrientation(); + if (requestedOrientation == ORIENTATION_UNDEFINED) { + return true; + } + // Compute parent orientation from bounds + final int orientation = parentBounds.height() >= parentBounds.width() + ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; + // Compute orientation from stable parent bounds (= parent bounds with insets applied) + final Task task = getTask(); + task.calculateInsetFrames(mTmpOutNonDecorBounds /* outNonDecorBounds */, + outStableBounds /* outStableBounds */, parentBounds /* bounds */, + mDisplayContent.getDisplayInfo()); + final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width() + ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; + // If orientation does not match the orientation with insets applied, then a + // display rotation will not be enough to respect orientation. However, even if they do + // not match but the orientation with insets applied matches the requested orientation, then + // there is no need to modify the bounds because when insets are applied, the activity will + // have the desired orientation. + final boolean orientationRespectedWithInsets = orientation == orientationWithInsets + || orientationWithInsets == requestedOrientation; + if (orientationRespectedWithInsets) { + outStableBounds.setEmpty(); + } + return orientationRespectedWithInsets; + } + + /** + * Computes bounds (letterbox or pillarbox) when either: + * 1. The parent doesn't handle the orientation change and the requested orientation is + * different from the parent (see {@link DisplayContent#setIgnoreOrientationRequest()}. + * 2. The parent handling the orientation is not enough. This occurs when the display rotation + * may not be enough to respect orientation requests (see {@link + * ActivityRecord#orientationRespectedWithInsets}). * * <p>If letterboxed due to fixed orientation then aspect ratio restrictions are also applied * in this method. @@ -7464,12 +7796,28 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig, int windowingMode) { mLetterboxBoundsForFixedOrientationAndAspectRatio = null; - if (handlesOrientationChangeFromDescendant()) { + final Rect parentBounds = newParentConfig.windowConfiguration.getBounds(); + final Rect stableBounds = new Rect(); + // If orientation is respected when insets are applied, then stableBounds will be empty. + boolean orientationRespectedWithInsets = + orientationRespectedWithInsets(parentBounds, stableBounds); + if (handlesOrientationChangeFromDescendant() && orientationRespectedWithInsets) { // No need to letterbox because of fixed orientation. Display will handle - // fixed-orientation requests. + // fixed-orientation requests and a display rotation is enough to respect requested + // orientation with insets applied. return; } - if (WindowConfiguration.inMultiWindowMode(windowingMode) && isResizeable()) { + // Not using Task#isResizeable() or ActivityRecord#isResizeable() directly because app + // compatibility testing showed that android:supportsPictureInPicture="true" alone is not + // sufficient signal for not letterboxing an app. + // TODO(214602463): Remove multi-window check since orientation and aspect ratio + // restrictions should always be applied in multi-window. + final boolean isResizeable = task != null + // Activity should be resizable if the task is. + ? task.isResizeable(/* checkPictureInPictureSupport */ false) + || isResizeable(/* checkPictureInPictureSupport */ false) + : isResizeable(/* checkPictureInPictureSupport */ false); + if (WindowConfiguration.inMultiWindowMode(windowingMode) && isResizeable) { // Ignore orientation request for resizable apps in multi window. return; } @@ -7486,7 +7834,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // If the activity requires a different orientation (either by override or activityInfo), // make it fit the available bounds by scaling down its bounds. final int forcedOrientation = getRequestedConfigurationOrientation(); - if (forcedOrientation == ORIENTATION_UNDEFINED || forcedOrientation == parentOrientation) { + + if (forcedOrientation == ORIENTATION_UNDEFINED + || (forcedOrientation == parentOrientation && orientationRespectedWithInsets)) { return; } @@ -7498,48 +7848,58 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return; } - final Rect parentBounds = newParentConfig.windowConfiguration.getBounds(); - final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds(); + // TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app + // bounds or stable bounds to unify aspect ratio logic. + final Rect parentBoundsWithInsets = orientationRespectedWithInsets + ? newParentConfig.windowConfiguration.getAppBounds() : stableBounds; final Rect containingBounds = new Rect(); - final Rect containingAppBounds = new Rect(); - // Need to shrink the containing bounds into a square because the parent orientation does - // not match the activity requested orientation. + final Rect containingBoundsWithInsets = new Rect(); + // Need to shrink the containing bounds into a square because the parent orientation + // does not match the activity requested orientation. if (forcedOrientation == ORIENTATION_LANDSCAPE) { - // Shrink height to match width. Position height within app bounds. - final int bottom = Math.min(parentAppBounds.top + parentBounds.width(), - parentAppBounds.bottom); - containingBounds.set(parentBounds.left, parentAppBounds.top, parentBounds.right, + // Landscape is defined as width > height. Make the container respect landscape + // orientation by shrinking height to one less than width. Landscape activity will be + // vertically centered within parent bounds with insets, so position vertical bounds + // within parent bounds with insets to prevent insets from unnecessarily trimming + // vertical bounds. + final int bottom = Math.min(parentBoundsWithInsets.top + parentBounds.width() - 1, + parentBoundsWithInsets.bottom); + containingBounds.set(parentBounds.left, parentBoundsWithInsets.top, parentBounds.right, bottom); - containingAppBounds.set(parentAppBounds.left, parentAppBounds.top, - parentAppBounds.right, bottom); + containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top, + parentBoundsWithInsets.right, bottom); } else { - // Shrink width to match height. Position width within app bounds. - final int right = Math.min(parentAppBounds.left + parentBounds.height(), - parentAppBounds.right); - containingBounds.set(parentAppBounds.left, parentBounds.top, right, + // Portrait is defined as width <= height. Make the container respect portrait + // orientation by shrinking width to match height. Portrait activity will be + // horizontally centered within parent bounds with insets, so position horizontal bounds + // within parent bounds with insets to prevent insets from unnecessarily trimming + // horizontal bounds. + final int right = Math.min(parentBoundsWithInsets.left + parentBounds.height(), + parentBoundsWithInsets.right); + containingBounds.set(parentBoundsWithInsets.left, parentBounds.top, right, parentBounds.bottom); - containingAppBounds.set(parentAppBounds.left, parentAppBounds.top, right, - parentAppBounds.bottom); + containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top, + right, parentBoundsWithInsets.bottom); } - Rect mTmpFullBounds = new Rect(resolvedBounds); + // Store the current bounds to be able to revert to size compat mode values below if needed. + final Rect prevResolvedBounds = new Rect(resolvedBounds); resolvedBounds.set(containingBounds); - // Override from config_fixedOrientationLetterboxAspectRatio or via ADB with - // set-fixed-orientation-letterbox-aspect-ratio. final float letterboxAspectRatioOverride = - mWmService.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(); + mLetterboxUiController.getFixedOrientationLetterboxAspectRatio(newParentConfig); final float desiredAspectRatio = letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO ? letterboxAspectRatioOverride : computeAspectRatio(parentBounds); // Apply aspect ratio to resolved bounds - mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingAppBounds, + mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingBoundsWithInsets, containingBounds, desiredAspectRatio, true); - // Vertically center if orientation is landscape. Bounds will later be horizontally centered - // in {@link updateResolvedBoundsHorizontalPosition()} regardless of orientation. + // Vertically center if orientation is landscape. Center within parent bounds with insets + // to ensure that insets do not trim height. Bounds will later be horizontally centered in + // {@link updateResolvedBoundsHorizontalPosition()} regardless of orientation. if (forcedOrientation == ORIENTATION_LANDSCAPE) { - final int offsetY = parentBounds.centerY() - resolvedBounds.centerY(); + final int offsetY = parentBoundsWithInsets.centerY() - resolvedBounds.centerY(); resolvedBounds.offset(0, offsetY); } @@ -7551,14 +7911,15 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // The app shouldn't be resized, we only do fixed orientation letterboxing if the // compat bounds are also from the same fixed orientation letterbox. Otherwise, // clear the fixed orientation bounds to show app in size compat mode. - resolvedBounds.set(mTmpFullBounds); + resolvedBounds.set(prevResolvedBounds); return; } } // Calculate app bounds using fixed orientation bounds because they will be needed later // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}. - task.computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig); + getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(), + newParentConfig); mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds); } @@ -7586,7 +7947,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) { // Compute the configuration based on the resolved bounds. If aspect ratio doesn't // restrict, the bounds should be the requested override bounds. - task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, + getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, getFixedRotationTransformDisplayInfo()); } } @@ -7646,10 +8007,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // Use resolvedBounds to compute other override configurations such as appBounds. The bounds // are calculated in compat container space. The actual position on screen will be applied // later, so the calculation is simpler that doesn't need to involve offset from parent. - task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, + getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, mCompatDisplayInsets); // Use current screen layout as source because the size of app is independent to parent. - resolvedConfig.screenLayout = Task.computeScreenLayoutOverride( + resolvedConfig.screenLayout = TaskFragment.computeScreenLayoutOverride( getConfiguration().screenLayout, resolvedConfig.screenWidthDp, resolvedConfig.screenHeightDp); @@ -7662,7 +8023,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // Below figure is an example that puts an activity which was launched in a larger container // into a smaller container. // The outermost rectangle is the real display bounds. - // "@" is the container app bounds (parent bounds or fixed orientation bouds) + // "@" is the container app bounds (parent bounds or fixed orientation bounds) // "#" is the {@code resolvedBounds} that applies to application. // "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled. // ------------------------------ @@ -7705,12 +8066,23 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe mSizeCompatBounds = null; } - // Align to top of parent (bounds) - this is a UX choice and exclude the horizontal decor - // if needed. Horizontal position is adjusted in updateResolvedBoundsHorizontalPosition. + // Vertically center within parent (bounds) - this is a UX choice and exclude the horizontal + // decor if needed. Horizontal position is adjusted in + // updateResolvedBoundsHorizontalPosition. // Above coordinates are in "@" space, now place "*" and "#" to screen space. final boolean fillContainer = resolvedBounds.equals(containingBounds); final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left; - final int screenPosY = containerBounds.top; + // If the activity is not in size compat mode, calculate vertical centering + // from the container and resolved bounds. + // If the activity is in size compat mode, calculate vertical centering + // from the container and size compat bounds. + // The container bounds contain the parent bounds offset in the display, for + // example when an activity is in the lower split of split screen. + final int screenPosY = (mSizeCompatBounds == null + ? (containerBounds.height() - resolvedBounds.height()) / 2 + : (containerBounds.height() - mSizeCompatBounds.height()) / 2) + + containerBounds.top; + if (screenPosX != 0 || screenPosY != 0) { if (mSizeCompatBounds != null) { mSizeCompatBounds.offset(screenPosX, screenPosY); @@ -7754,13 +8126,14 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return false; } } - if (info.getMinAspectRatio() > 0) { + final float minAspectRatio = getMinAspectRatio(); + if (minAspectRatio > 0) { // The activity should have at least the min aspect ratio, so this checks if the // container still has available space to provide larger aspect ratio. final float containerAspectRatio = (0.5f + Math.max(containerAppWidth, containerAppHeight)) / Math.min(containerAppWidth, containerAppHeight); - if (containerAspectRatio <= info.getMinAspectRatio()) { + if (containerAspectRatio <= minAspectRatio) { // The long side has reached the parent. return false; } @@ -7793,12 +8166,16 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe if (getUid() == SYSTEM_UID) { return false; } + // Do not sandbox to activity window bounds if the feature is disabled. + if (mDisplayContent != null && !mDisplayContent.sandboxDisplayApis()) { + return false; + } // Never apply sandboxing to an app that should be explicitly excluded from the config. - if (info != null && info.neverSandboxDisplayApis()) { + if (info.neverSandboxDisplayApis(sConstrainDisplayApisConfig)) { return false; } // Always apply sandboxing to an app that should be explicitly included from the config. - if (info != null && info.alwaysSandboxDisplayApis()) { + if (info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig)) { return true; } // Max bounds should be sandboxed when an activity should have compatDisplayInsets, and it @@ -7817,13 +8194,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe @VisibleForTesting @Override Rect getAnimationBounds(int appRootTaskClipMode) { - if (appRootTaskClipMode == ROOT_TASK_CLIP_BEFORE_ANIM && getRootTask() != null) { - // Using the root task bounds here effectively applies the clipping before animation. - return getRootTask().getBounds(); - } - // Use task-bounds if available so that activity-level letterbox (maxAspectRatio) is + // Use TaskFragment-bounds if available so that activity-level letterbox (maxAspectRatio) is // included in the animation. - return task != null ? task.getBounds() : getBounds(); + final TaskFragment taskFragment = getTaskFragment(); + return taskFragment != null ? taskFragment.getBounds() : getBounds(); } @Override @@ -7970,11 +8344,15 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe Rect containingBounds, float desiredAspectRatio, boolean fixedOrientationLetterboxed) { final float maxAspectRatio = info.getMaxAspectRatio(); final Task rootTask = getRootTask(); - final float minAspectRatio = info.getMinAspectRatio(); - + final float minAspectRatio = getMinAspectRatio(); + // Not using ActivityRecord#isResizeable() directly because app compatibility testing + // showed that android:supportsPictureInPicture="true" alone is not sufficient signal for + // not letterboxing an app. + // TODO(214602463): Remove multi-window check since orientation and aspect ratio + // restrictions should always be applied in multi-window. if (task == null || rootTask == null - || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets() - && !fixedOrientationLetterboxed) + || (inMultiWindowMode() && isResizeable(/* checkPictureInPictureSupport */ false) + && !fixedOrientationLetterboxed) || (maxAspectRatio < 1 && minAspectRatio < 1 && desiredAspectRatio < 1) || isInVrUiMode(getConfiguration())) { // We don't enforce aspect ratio if the activity task is in multiwindow unless it is in @@ -8067,7 +8445,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe // If the bounds are restricted by fixed aspect ratio, then out bounds should be put in the // container app bounds. Otherwise the entire container bounds are available. if (!outBounds.equals(containingBounds)) { - // The horizontal position should not cover insets. + // The horizontal position should not cover insets (e.g. display cutout). outBounds.left = containingAppBounds.left; } @@ -8075,6 +8453,20 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } /** + * Returns the min aspect ratio of this activity. + */ + private float getMinAspectRatio() { + return info.getMinAspectRatio(getRequestedOrientation()); + } + + /** + * Returns true if the activity has maximum or minimum aspect ratio. + */ + private boolean hasFixedAspectRatio() { + return info.hasFixedAspectRatio(getRequestedOrientation()); + } + + /** * Returns the aspect ratio of the given {@code rect}. */ static float computeAspectRatio(Rect rect) { @@ -8225,7 +8617,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe configChangeFlags |= changes; startFreezingScreenLocked(globalChanges); forceNewConfig = false; - preserveWindow &= isResizeOnlyChange(changes); + // Do not preserve window if it is freezing screen because the original window won't be + // able to update drawn state that causes freeze timeout. + preserveWindow &= isResizeOnlyChange(changes) && !mFreezingScreen; final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged()); if (hasResizeChange) { final boolean isDragResizing = task.isDragResizing(); @@ -8803,6 +9197,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe } proto.write(PIP_AUTO_ENTER_ENABLED, pictureInPictureArgs.isAutoEnterEnabled()); proto.write(IN_SIZE_COMPAT_MODE, inSizeCompatMode()); + proto.write(MIN_ASPECT_RATIO, getMinAspectRatio()); + // Only record if max bounds sandboxing is applied, if the caller has the necessary + // permission to access the device configs. + proto.write(PROVIDES_MAX_BOUNDS, providesMaxBounds()); } @Override @@ -8841,6 +9239,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe * compatibility mode activity compute the configuration without relying on its current display. */ static class CompatDisplayInsets { + /** The original rotation the compat insets were computed in */ + final @Rotation int mOriginalRotation; /** The container width on rotation 0. */ private final int mWidth; /** The container height on rotation 0. */ @@ -8867,6 +9267,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe /** Constructs the environment to simulate the bounds behavior of the given container. */ CompatDisplayInsets(DisplayContent display, ActivityRecord container, @Nullable Rect fixedOrientationBounds) { + mOriginalRotation = display.getRotation(); mIsFloating = container.getWindowConfiguration().tasksAreFloating(); if (mIsFloating) { final Rect containerBounds = container.getWindowConfiguration().getBounds(); @@ -9013,7 +9414,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe outAppBounds.offset(insets.left, insets.top); } else if (rotation != ROTATION_UNDEFINED) { // Ensure the app bounds won't overlap with insets. - Task.intersectWithInsetsIfFits(outAppBounds, outBounds, mNonDecorInsets[rotation]); + TaskFragment.intersectWithInsetsIfFits(outAppBounds, outBounds, + mNonDecorInsets[rotation]); } } } @@ -9036,17 +9438,19 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return null; } final Rect insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets( - task.getBounds(), Type.systemBars(), false /* ignoreVisibility */); + task.getBounds(), Type.systemBars(), false /* ignoreVisibility */).toRect(); InsetUtils.addInsets(insets, getLetterboxInsets()); - return new RemoteAnimationTarget(task.mTaskId, record.getMode(), - record.mAdapter.mCapturedLeash, !fillsParent(), + final RemoteAnimationTarget target = new RemoteAnimationTarget(task.mTaskId, + record.getMode(), record.mAdapter.mCapturedLeash, !fillsParent(), new Rect(), insets, getPrefixOrderIndex(), record.mAdapter.mPosition, record.mAdapter.mLocalBounds, - record.mAdapter.mRootTaskBounds, task.getWindowConfiguration(), + record.mAdapter.mEndBounds, task.getWindowConfiguration(), false /*isNotInRecents*/, record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null, - record.mStartBounds, task.getTaskInfo()); + record.mStartBounds, task.getTaskInfo(), checkEnterPictureInPictureAppOpsState()); + target.hasAnimatingParent = record.hasAnimatingParent(); + return target; } @Override @@ -9082,6 +9486,19 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe return false; } + @Override + void finishSync(Transaction outMergedTransaction, boolean cancel) { + // This override is just for getting metrics. allFinished needs to be checked before + // finish because finish resets all the states. + mLastAllReadyAtSync = allSyncFinished(); + super.finishSync(outMergedTransaction, cancel); + } + + @Override + boolean canBeAnimationTarget() { + return true; + } + static class Builder { private final ActivityTaskManagerService mAtmService; private WindowProcessController mCallerApp; diff --git a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java index 8540fa7cf347..30c7b232fcc8 100644 --- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java +++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java @@ -16,10 +16,10 @@ package com.android.server.wm; +import static com.android.server.wm.ActivityRecord.State.PAUSING; +import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; -import static com.android.server.wm.Task.ActivityState.PAUSING; -import static com.android.server.wm.Task.ActivityState.RESUMED; import android.util.ArraySet; import android.util.Slog; diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index b6f2f243040e..bb5d962760e7 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -27,6 +27,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.IApplicationThread; @@ -38,6 +39,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.UserHandle; import android.provider.Settings; @@ -488,6 +490,32 @@ public class ActivityStartController { return START_SUCCESS; } + /** + * Starts an activity in the TaskFragment. + * @param taskFragment TaskFragment {@link TaskFragment} to start the activity in. + * @param activityIntent intent to start the activity. + * @param activityOptions ActivityOptions to start the activity with. + * @param resultTo the caller activity + * @param callingUid the caller uid + * @param callingPid the caller pid + * @return the start result. + */ + int startActivityInTaskFragment(@NonNull TaskFragment taskFragment, + @NonNull Intent activityIntent, @Nullable Bundle activityOptions, + @Nullable IBinder resultTo, int callingUid, int callingPid) { + final ActivityRecord caller = + resultTo != null ? ActivityRecord.forTokenLocked(resultTo) : null; + return obtainStarter(activityIntent, "startActivityInTaskFragment") + .setActivityOptions(activityOptions) + .setInTaskFragment(taskFragment) + .setResultTo(resultTo) + .setRequestCode(-1) + .setCallingUid(callingUid) + .setCallingPid(callingPid) + .setUserId(caller != null ? caller.mUserId : mService.getCurrentUserId()) + .execute(); + } + void registerRemoteAnimationForNextActivityStart(String packageName, RemoteAnimationAdapter adapter) { mPendingRemoteAnimationRegistry.addPendingAnimation(packageName, adapter); diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index 979cea9b1569..223f0be9bbea 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -51,6 +51,7 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.BlockedAppActivity; @@ -178,7 +179,33 @@ class ActivityStartInterceptor { // before issuing the work challenge. return true; } - return interceptLockedManagedProfileIfNeeded(); + if (interceptLockedManagedProfileIfNeeded()) { + return true; + } + + final SparseArray<ActivityInterceptorCallback> callbacks = + mService.getActivityInterceptorCallbacks(); + final ActivityInterceptorCallback.ActivityInterceptorInfo interceptorInfo = + new ActivityInterceptorCallback.ActivityInterceptorInfo(mRealCallingUid, + mRealCallingPid, mUserId, mCallingPackage, mCallingFeatureId, mIntent, + mRInfo, mAInfo, mResolvedType, mCallingPid, mCallingUid, + mActivityOptions); + + for (int i = 0; i < callbacks.size(); i++) { + final ActivityInterceptorCallback callback = callbacks.valueAt(i); + final Intent newIntent = callback.intercept(interceptorInfo); + if (newIntent == null) { + continue; + } + mIntent = newIntent; + mCallingPid = mRealCallingPid; + mCallingUid = mRealCallingUid; + mRInfo = mSupervisor.resolveIntent(mIntent, null, mUserId, 0, mRealCallingUid); + mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, + null /*profilerInfo*/); + return true; + } + return false; } private boolean hasCrossProfileAnimation() { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index cd2b8c21f57d..606acf02eaa4 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -23,11 +23,13 @@ import static android.app.ActivityManager.START_CANCELED; import static android.app.ActivityManager.START_CLASS_NOT_FOUND; import static android.app.ActivityManager.START_DELIVERED_TO_TOP; import static android.app.ActivityManager.START_FLAG_ONLY_IF_NEEDED; +import static android.app.ActivityManager.START_PERMISSION_DENIED; import static android.app.ActivityManager.START_RETURN_INTENT_TO_CALLER; import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -58,6 +60,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS; +import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS; @@ -69,12 +72,13 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_USER_ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.ActivityTaskManagerService.ANIMATE; +import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW; +import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY; import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME; import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS; import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY; -import static com.android.server.wm.Task.ActivityState.RESUMED; import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -113,7 +117,7 @@ import android.util.ArraySet; import android.util.DebugUtils; import android.util.Pools.SynchronizedPool; import android.util.Slog; -import android.window.IRemoteTransition; +import android.window.RemoteTransition; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.HeavyWeightSwitcherActivity; @@ -178,6 +182,7 @@ class ActivityStarter { private int mPreferredWindowingMode; private Task mInTask; + private TaskFragment mInTaskFragment; @VisibleForTesting boolean mAddingToTask; private Task mReuseTask; @@ -191,7 +196,6 @@ class ActivityStarter { private Task mTargetTask; private boolean mMovedToFront; private boolean mNoAnimation; - private boolean mKeepCurTransition; private boolean mAvoidMoveToFront; private boolean mFrozeTaskList; private boolean mTransientLaunch; @@ -345,6 +349,7 @@ class ActivityStarter { boolean avoidMoveToFront; ActivityRecord[] outActivity; Task inTask; + TaskFragment inTaskFragment; String reason; ProfilerInfo profilerInfo; Configuration globalConfig; @@ -395,6 +400,7 @@ class ActivityStarter { componentSpecified = false; outActivity = null; inTask = null; + inTaskFragment = null; reason = null; profilerInfo = null; globalConfig = null; @@ -410,7 +416,7 @@ class ActivityStarter { /** * Adopts all values from passed in request. */ - void set(Request request) { + void set(@NonNull Request request) { caller = request.caller; intent = request.intent; intentGrants = request.intentGrants; @@ -435,6 +441,7 @@ class ActivityStarter { componentSpecified = request.componentSpecified; outActivity = request.outActivity; inTask = request.inTask; + inTaskFragment = request.inTaskFragment; reason = request.reason; profilerInfo = request.profilerInfo; globalConfig = request.globalConfig; @@ -578,6 +585,7 @@ class ActivityStarter { mPreferredWindowingMode = starter.mPreferredWindowingMode; mInTask = starter.mInTask; + mInTaskFragment = starter.mInTaskFragment; mAddingToTask = starter.mAddingToTask; mReuseTask = starter.mReuseTask; @@ -589,7 +597,6 @@ class ActivityStarter { mTargetRootTask = starter.mTargetRootTask; mMovedToFront = starter.mMovedToFront; mNoAnimation = starter.mNoAnimation; - mKeepCurTransition = starter.mKeepCurTransition; mAvoidMoveToFront = starter.mAvoidMoveToFront; mFrozeTaskList = starter.mFrozeTaskList; @@ -741,7 +748,7 @@ class ActivityStarter { Slog.w(TAG, "Unable to find app for caller " + mRequest.caller + " (pid=" + mRequest.callingPid + ") when starting: " + mRequest.intent.toString()); SafeActivityOptions.abort(mRequest.activityOptions); - return ActivityManager.START_PERMISSION_DENIED; + return START_PERMISSION_DENIED; } } @@ -839,6 +846,7 @@ class ActivityStarter { final int startFlags = request.startFlags; final SafeActivityOptions options = request.activityOptions; Task inTask = request.inTask; + TaskFragment inTaskFragment = request.inTaskFragment; int err = ActivityManager.START_SUCCESS; // Pull the optional Ephemeral Installer-only bundle out of the options early. @@ -854,7 +862,7 @@ class ActivityStarter { } else { Slog.w(TAG, "Unable to find app for caller " + caller + " (pid=" + callingPid + ") when starting: " + intent.toString()); - err = ActivityManager.START_PERMISSION_DENIED; + err = START_PERMISSION_DENIED; } } @@ -868,7 +876,7 @@ class ActivityStarter { ActivityRecord sourceRecord = null; ActivityRecord resultRecord = null; if (resultTo != null) { - sourceRecord = mRootWindowContainer.isInAnyTask(resultTo); + sourceRecord = ActivityRecord.isInAnyTask(resultTo); if (DEBUG_RESULTS) { Slog.v(TAG_RESULTS, "Will send result to " + resultTo + " " + sourceRecord); } @@ -1179,8 +1187,8 @@ class ActivityStarter { } mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession, - request.voiceInteractor, startFlags, true /* doResume */, checkedOptions, inTask, - restrictedBgActivity, intentGrants); + request.voiceInteractor, startFlags, true /* doResume */, checkedOptions, + inTask, inTaskFragment, restrictedBgActivity, intentGrants); if (request.outActivity != null) { request.outActivity[0] = mLastStartActivityRecord; @@ -1273,7 +1281,7 @@ class ActivityStarter { // This is used to block background activity launch even if the app is still // visible to user after user clicking home button. - final boolean appSwitchAllowed = mService.getBalAppSwitchesAllowed(); + final int appSwitchState = mService.getBalAppSwitchesState(); // don't abort if the callingUid has a visible window or is a persistent system process final int callingUidProcState = mService.mActiveUids.getUidState(callingUid); @@ -1286,7 +1294,9 @@ class ActivityStarter { // Normal apps with visible app window will be allowed to start activity if app switching // is allowed, or apps like live wallpaper with non app visible window will be allowed. - if (((appSwitchAllowed || mService.mActiveUids.hasNonAppVisibleWindow(callingUid)) + final boolean appSwitchAllowedOrFg = + appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY; + if (((appSwitchAllowedOrFg || mService.mActiveUids.hasNonAppVisibleWindow(callingUid)) && callingUidHasAnyVisibleWindow) || isCallingUidPersistentSystemProcess) { if (DEBUG_ACTIVITY_STARTS) { @@ -1396,7 +1406,7 @@ class ActivityStarter { // don't abort if the callerApp or other processes of that uid are allowed in any way if (callerApp != null) { // first check the original calling process - if (callerApp.areBackgroundActivityStartsAllowed(appSwitchAllowed)) { + if (callerApp.areBackgroundActivityStartsAllowed(appSwitchState)) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Background activity start allowed: callerApp process (pid = " + callerApp.getPid() + ", uid = " + callerAppUid + ") is allowed"); @@ -1410,7 +1420,7 @@ class ActivityStarter { for (int i = uidProcesses.size() - 1; i >= 0; i--) { final WindowProcessController proc = uidProcesses.valueAt(i); if (proc != callerApp - && proc.areBackgroundActivityStartsAllowed(appSwitchAllowed)) { + && proc.areBackgroundActivityStartsAllowed(appSwitchState)) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Background activity start allowed: process " + proc.getPid() @@ -1424,7 +1434,7 @@ class ActivityStarter { // anything that has fallen through would currently be aborted Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage + "; callingUid: " + callingUid - + "; appSwitchAllowed: " + appSwitchAllowed + + "; appSwitchState: " + appSwitchState + "; isCallingUidForeground: " + isCallingUidForeground + "; callingUidHasAnyVisibleWindow: " + callingUidHasAnyVisibleWindow + "; callingUidProcState: " + DebugUtils.valueToString(ActivityManager.class, @@ -1550,29 +1560,43 @@ class ActivityStarter { * Here also ensures that the starting activity is removed if the start wasn't successful. */ private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord, - IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, - int startFlags, boolean doResume, ActivityOptions options, Task inTask, - boolean restrictedBgActivity, NeededUriGrants intentGrants) { + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + int startFlags, boolean doResume, ActivityOptions options, Task inTask, + TaskFragment inTaskFragment, boolean restrictedBgActivity, + NeededUriGrants intentGrants) { int result = START_CANCELED; + boolean startResultSuccessful = false; final Task startedActivityRootTask; // Create a transition now to record the original intent of actions taken within // startActivityInner. Otherwise, logic in startActivityInner could start a different // transition based on a sub-action. // Only do the create here (and defer requestStart) since startActivityInner might abort. - final Transition newTransition = (!mService.getTransitionController().isCollecting() - && mService.getTransitionController().getTransitionPlayer() != null) - ? mService.getTransitionController().createTransition(TRANSIT_OPEN) : null; - IRemoteTransition remoteTransition = r.takeRemoteTransition(); + final TransitionController transitionController = r.mTransitionController; + Transition newTransition = (!transitionController.isCollecting() + && transitionController.getTransitionPlayer() != null) + ? transitionController.createTransition(TRANSIT_OPEN) : null; + RemoteTransition remoteTransition = r.takeRemoteTransition(); if (newTransition != null && remoteTransition != null) { newTransition.setRemoteTransition(remoteTransition); } - mService.getTransitionController().collect(r); + transitionController.collect(r); + final boolean isTransient = r.getOptions() != null && r.getOptions().getTransientLaunch(); try { mService.deferWindowLayout(); Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner"); result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor, - startFlags, doResume, options, inTask, restrictedBgActivity, intentGrants); + startFlags, doResume, options, inTask, inTaskFragment, restrictedBgActivity, + intentGrants); + startResultSuccessful = ActivityManager.isStartResultSuccessful(result); + final boolean taskAlwaysOnTop = options != null && options.getTaskAlwaysOnTop(); + // Apply setAlwaysOnTop when starting an Activity is successful regardless of creating + // a new Activity or recycling the existing Activity. + if (taskAlwaysOnTop && startResultSuccessful) { + final Task targetRootTask = + mTargetRootTask != null ? mTargetRootTask : mTargetTask.getRootTask(); + targetRootTask.setAlwaysOnTop(true); + } } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); startedActivityRootTask = handleStartResult(r, result); @@ -1580,7 +1604,7 @@ class ActivityStarter { mSupervisor.mUserLeaving = false; // Transition housekeeping - if (!ActivityManager.isStartResultSuccessful(result)) { + if (!startResultSuccessful) { if (newTransition != null) { newTransition.abort(); } @@ -1599,17 +1623,27 @@ class ActivityStarter { statusBar.collapsePanels(); } } - if (result == START_SUCCESS || result == START_TASK_TO_FRONT) { + final boolean started = result == START_SUCCESS || result == START_TASK_TO_FRONT; + if (started) { // The activity is started new rather than just brought forward, so record // it as an existence change. - mService.getTransitionController().collectExistenceChange(r); + transitionController.collectExistenceChange(r); + } else if (result == START_DELIVERED_TO_TOP && newTransition != null) { + // We just delivered to top, so there isn't an actual transition here + newTransition.abort(); + newTransition = null; + } + if (isTransient) { + // `r` isn't guaranteed to be the actual relevant activity, so we must wait + // until after we launched to identify the relevant activity. + transitionController.setTransientLaunch(mLastStartActivityRecord); } if (newTransition != null) { - mService.getTransitionController().requestStartTransition(newTransition, + transitionController.requestStartTransition(newTransition, mTargetTask, remoteTransition); - } else { + } else if (started) { // Make the collecting transition wait until this request is ready. - mService.getTransitionController().setReady(false); + transitionController.setReady(r, false); } } } @@ -1669,15 +1703,15 @@ class ActivityStarter { * * Note: This method should only be called from {@link #startActivityUnchecked}. */ - // TODO(b/152429287): Make it easier to exercise code paths through startActivityInner @VisibleForTesting int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, boolean doResume, ActivityOptions options, Task inTask, - boolean restrictedBgActivity, NeededUriGrants intentGrants) { - setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession, - voiceInteractor, restrictedBgActivity); + TaskFragment inTaskFragment, boolean restrictedBgActivity, + NeededUriGrants intentGrants) { + setInitialState(r, options, inTask, inTaskFragment, doResume, startFlags, sourceRecord, + voiceSession, voiceInteractor, restrictedBgActivity); computeLaunchingTaskFlags(); @@ -1685,6 +1719,8 @@ class ActivityStarter { mIntent.setFlags(mLaunchFlags); + // Get top task at beginning because the order may be changed when reusing existing task. + final Task prevTopTask = mPreferredTaskDisplayArea.getFocusedRootTask(); final Task reusedTask = getReusableTask(); // If requested, freeze the task list @@ -1759,11 +1795,6 @@ class ActivityStarter { if (!mAvoidMoveToFront && mDoResume) { mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask); - if (mOptions != null) { - if (mOptions.getTaskAlwaysOnTop()) { - mTargetRootTask.setAlwaysOnTop(true); - } - } if (!mTargetRootTask.isTopRootTaskInDisplayArea() && mService.mInternal.isDreaming()) { // Launching underneath dream activity (fullscreen, always-on-top). Run the launch- // -behind transition so the Activity gets created and starts in visible state. @@ -1785,24 +1816,23 @@ class ActivityStarter { UserHandle.getAppId(mStartActivity.info.applicationInfo.uid) /*recipient*/, resultToUid /*visible*/, true /*direct*/); } + final Task startedTask = mStartActivity.getTask(); if (newTask) { - EventLogTags.writeWmCreateTask(mStartActivity.mUserId, - mStartActivity.getTask().mTaskId); + EventLogTags.writeWmCreateTask(mStartActivity.mUserId, startedTask.mTaskId); } - mStartActivity.logStartActivity( - EventLogTags.WM_CREATE_ACTIVITY, mStartActivity.getTask()); + mStartActivity.logStartActivity(EventLogTags.WM_CREATE_ACTIVITY, startedTask); - mTargetRootTask.mLastPausedActivity = null; + mStartActivity.getTaskFragment().clearLastPausedActivity(); mRootWindowContainer.startPowerModeLaunchIfNeeded( false /* forceSend */, mStartActivity); + final boolean isTaskSwitch = startedTask != prevTopTask && !startedTask.isEmbedded(); mTargetRootTask.startActivityLocked(mStartActivity, topRootTask != null ? topRootTask.getTopNonFinishingActivity() : null, newTask, - mKeepCurTransition, mOptions, sourceRecord); + isTaskSwitch, mOptions, sourceRecord); if (mDoResume) { - final ActivityRecord topTaskActivity = - mStartActivity.getTask().topRunningActivityLocked(); + final ActivityRecord topTaskActivity = startedTask.topRunningActivityLocked(); if (!mTargetRootTask.isTopActivityFocusable() || (topTaskActivity != null && topTaskActivity.isTaskOverlay() && mStartActivity != topTaskActivity)) { @@ -1836,8 +1866,8 @@ class ActivityStarter { mRootWindowContainer.updateUserRootTask(mStartActivity.mUserId, mTargetRootTask); // Update the recent tasks list immediately when the activity starts - mSupervisor.mRecentTasks.add(mStartActivity.getTask()); - mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), + mSupervisor.mRecentTasks.add(startedTask); + mSupervisor.handleNonResizableTaskIfNeeded(startedTask, mPreferredWindowingMode, mPreferredTaskDisplayArea, mTargetRootTask); return START_SUCCESS; @@ -1951,10 +1981,46 @@ class ActivityStarter { } } + if (mInTaskFragment != null && !canEmbedActivity(mInTaskFragment, r, newTask, targetTask)) { + Slog.e(TAG, "Permission denied: Cannot embed " + r + " to " + mInTaskFragment.getTask() + + " targetTask= " + targetTask); + return START_PERMISSION_DENIED; + } + return START_SUCCESS; } /** + * Return {@code true} if an activity can be embedded to the TaskFragment. + * @param taskFragment the TaskFragment for embedding. + * @param starting the starting activity. + * @param newTask whether the starting activity is going to be launched on a new task. + * @param targetTask the target task for launching activity, which could be different from + * the one who hosting the embedding. + */ + private boolean canEmbedActivity(@NonNull TaskFragment taskFragment, ActivityRecord starting, + boolean newTask, Task targetTask) { + final Task hostTask = taskFragment.getTask(); + if (hostTask == null) { + return false; + } + + // Allowing the embedding if the task is owned by system. + final int hostUid = hostTask.effectiveUid; + if (UserHandle.getAppId(hostUid) == Process.SYSTEM_UID) { + return true; + } + + // Not allowed embedding an activity of another app. + if (hostUid != starting.getUid()) { + return false; + } + + // Not allowed embedding task. + return !newTask && (targetTask == null || targetTask == hostTask); + } + + /** * Prepare the target task to be reused for this launch, which including: * - Position the target task on valid root task on preferred display. * - Comply to the specified activity launch flags @@ -2043,7 +2109,7 @@ class ActivityStarter { // We didn't do anything... but it was needed (a.k.a., client don't use that intent!) // And for paranoia, make sure we have correctly resumed the top activity. resumeTargetRootTaskIfNeeded(); - + mLastStartActivityRecord = targetTaskTop; return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP; } @@ -2069,7 +2135,7 @@ class ActivityStarter { } // For paranoia, make sure we have correctly resumed the top activity. - topRootTask.mLastPausedActivity = null; + top.getTaskFragment().clearLastPausedActivity(); if (mDoResume) { mRootWindowContainer.resumeFocusedTasksTopActivities(); } @@ -2165,7 +2231,7 @@ class ActivityStarter { task.moveActivityToFrontLocked(act); act.updateOptionsLocked(mOptions); deliverNewIntent(act, intentGrants); - mTargetRootTask.mLastPausedActivity = null; + act.getTaskFragment().clearLastPausedActivity(); } else { mAddingToTask = true; } @@ -2233,6 +2299,7 @@ class ActivityStarter { mPreferredWindowingMode = WINDOWING_MODE_UNDEFINED; mInTask = null; + mInTaskFragment = null; mAddingToTask = false; mReuseTask = null; @@ -2244,7 +2311,6 @@ class ActivityStarter { mTargetTask = null; mMovedToFront = false; mNoAnimation = false; - mKeepCurTransition = false; mAvoidMoveToFront = false; mFrozeTaskList = false; mTransientLaunch = false; @@ -2260,9 +2326,9 @@ class ActivityStarter { } private void setInitialState(ActivityRecord r, ActivityOptions options, Task inTask, - boolean doResume, int startFlags, ActivityRecord sourceRecord, - IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, - boolean restrictedBgActivity) { + TaskFragment inTaskFragment, boolean doResume, int startFlags, + ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, + IVoiceInteractor voiceInteractor, boolean restrictedBgActivity) { reset(false /* clearRequest */); mStartActivity = r; @@ -2325,7 +2391,7 @@ class ActivityStarter { // of this in the record so that we can skip it when trying to find // the top running activity. mDoResume = doResume; - if (!doResume || !r.okToShowLocked() || mLaunchTaskBehind) { + if (!doResume || !r.showToCurrentUser() || mLaunchTaskBehind) { r.delayedResume = true; mDoResume = false; } @@ -2352,6 +2418,11 @@ class ActivityStarter { } mTransientLaunch = mOptions.getTransientLaunch(); mTargetRootTask = Task.fromWindowContainerToken(mOptions.getLaunchRootTask()); + + if (inTaskFragment == null) { + inTaskFragment = TaskFragment.fromTaskFragmentToken( + mOptions.getLaunchTaskFragmentToken(), mService); + } } mNotTop = (mLaunchFlags & FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? sourceRecord : null; @@ -2365,6 +2436,7 @@ class ActivityStarter { Slog.w(TAG, "Starting activity in task not in recents: " + inTask); mInTask = null; } + mInTaskFragment = inTaskFragment; mStartFlags = startFlags; // If the onlyIfNeeded flag is set, then we can do this if the activity being launched @@ -2588,13 +2660,28 @@ class ActivityStarter { /** * Figure out which task and activity to bring to front when we have found an existing matching * activity record in history. May also clear the task if needed. + * * @param intentActivity Existing matching activity. * @return {@link ActivityRecord} brought to front. */ private void setTargetRootTaskIfNeeded(ActivityRecord intentActivity) { - mTargetRootTask = intentActivity.getRootTask(); - mTargetRootTask.mLastPausedActivity = null; + intentActivity.getTaskFragment().clearLastPausedActivity(); Task intentTask = intentActivity.getTask(); + + // Only update the target-root-task when it is not indicated. + if (mTargetRootTask == null) { + if (mSourceRecord != null && mSourceRecord.mLaunchRootTask != null) { + // Inherit the target-root-task from source to ensure trampoline activities will be + // launched into the same root task. + mTargetRootTask = Task.fromWindowContainerToken(mSourceRecord.mLaunchRootTask); + } else { + final Task launchRootTask = + getLaunchRootTask(mStartActivity, mLaunchFlags, intentTask, mOptions); + mTargetRootTask = + launchRootTask != null ? launchRootTask : intentActivity.getRootTask(); + } + } + // If the target task is not in the front, then we need to bring it to the front... // except... well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have // the same behavior as if a new instance was being started, which means not bringing it @@ -2622,16 +2709,14 @@ class ActivityStarter { intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask()); } - final Task launchRootTask = getLaunchRootTask(mStartActivity, mLaunchFlags, - intentTask, mOptions); - if (launchRootTask == null || launchRootTask == mTargetRootTask) { + if (mTargetRootTask == intentActivity.getRootTask()) { // TODO(b/151572268): Figure out a better way to move tasks in above 2-levels // tasks hierarchies. if (mTargetRootTask != intentTask && mTargetRootTask != intentTask.getParent().asTask()) { intentTask.getParent().positionChildAt(POSITION_TOP, intentTask, false /* includingParents */); - intentTask = intentTask.getParent().asTask(); + intentTask = intentTask.getParent().asTaskFragment().getTask(); } // If the task is in multi-windowing mode, the activity may already be on // the top (visible to user but not the global top), then the result code @@ -2647,8 +2732,12 @@ class ActivityStarter { mStartActivity.appTimeTracker, DEFER_RESUME, "bringingFoundTaskToFront"); mMovedToFront = !wasTopOfVisibleRootTask; - } else { - intentTask.reparent(launchRootTask, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT, + } else if (intentActivity.getWindowingMode() != WINDOWING_MODE_PINNED) { + // Leaves reparenting pinned task operations to task organizer to make sure it + // dismisses pinned task properly. + // TODO(b/199997762): Consider leaving all reparent operation of organized tasks + // to task organizer. + intentTask.reparent(mTargetRootTask, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT, ANIMATE, DEFER_RESUME, "reparentToTargetRootTask"); mMovedToFront = true; } @@ -2671,6 +2760,19 @@ class ActivityStarter { mTargetRootTask = intentActivity.getRootTask(); mSupervisor.handleNonResizableTaskIfNeeded(intentTask, WINDOWING_MODE_UNDEFINED, mRootWindowContainer.getDefaultTaskDisplayArea(), mTargetRootTask); + + // We need to check if there is a launch root task in TDA for this target root task. + // If it exist, we need to reparent target root task from TDA to launch root task. + final TaskDisplayArea tda = mTargetRootTask.getDisplayArea(); + final Task launchRootTask = tda.getLaunchRootTask(mTargetRootTask.getWindowingMode(), + mTargetRootTask.getActivityType(), null /** options */, mSourceRootTask, + mLaunchFlags); + // If target root task is created by organizer, let organizer handle reparent itself. + if (!mTargetRootTask.mCreatedByOrganizer && launchRootTask != null + && launchRootTask != mTargetRootTask) { + mTargetRootTask.reparent(launchRootTask, POSITION_TOP); + mTargetRootTask = launchRootTask; + } } private void resumeTargetRootTaskIfNeeded() { @@ -2698,7 +2800,7 @@ class ActivityStarter { mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info, mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession, mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions); - mService.getTransitionController().collectExistenceChange(task); + task.mTransitionController.collectExistenceChange(task); addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask"); ProtoLog.v(WM_DEBUG_TASKS, "Starting new activity %s in new task %s", @@ -2720,7 +2822,7 @@ class ActivityStarter { mIntentDelivered = true; } - private void addOrReparentStartingActivity(Task parent, String reason) { + private void addOrReparentStartingActivity(@NonNull Task task, String reason) { String packageName= mService.mContext.getPackageName(); if (mPerf != null) { if (mPerf.getPerfHalVersion() >= BoostFramework.PERF_HAL_V23) { @@ -2737,13 +2839,50 @@ class ActivityStarter { packageName, -1, BoostFramework.Launch.BOOST_V1); } } - if (mStartActivity.getTask() == null || mStartActivity.getTask() == parent) { - parent.addChild(mStartActivity); + TaskFragment newParent = task; + if (mInTaskFragment != null) { + // mInTaskFragment is created and added to the leaf task by task fragment organizer's + // request. If the task was resolved and different than mInTaskFragment, reparent the + // task to mInTaskFragment for embedding. + if (mInTaskFragment.getTask() != task) { + if (shouldReparentInTaskFragment(task)) { + task.reparent(mInTaskFragment, POSITION_TOP); + } + } else { + newParent = mInTaskFragment; + } + } else { + final ActivityRecord top = task.topRunningActivity(false /* focusableOnly */, + false /* includingEmbeddedTask */); + final TaskFragment taskFragment = top != null ? top.getTaskFragment() : null; + if (taskFragment != null && taskFragment.isEmbedded() + && canEmbedActivity(taskFragment, mStartActivity, false /* newTask */, task)) { + // Use the embedded TaskFragment of the top activity as the new parent if the + // activity can be embedded. + newParent = top.getTaskFragment(); + } + } + + if (mStartActivity.getTaskFragment() == null + || mStartActivity.getTaskFragment() == newParent) { + newParent.addChild(mStartActivity, POSITION_TOP); } else { - mStartActivity.reparent(parent, parent.getChildCount() /* top */, reason); + mStartActivity.reparent(newParent, newParent.getChildCount() /* top */, reason); } } + private boolean shouldReparentInTaskFragment(Task task) { + // The task has not been embedded. We should reparent the task to TaskFragment. + if (!task.isEmbedded()) { + return true; + } + WindowContainer<?> parent = task.getParent(); + // If the Activity is going to launch on top of embedded Task in the same TaskFragment, + // we don't need to reparent the Task. Otherwise, the embedded Task should reparent to + // another TaskFragment. + return parent.asTaskFragment() != mInTaskFragment; + } + private int adjustLaunchFlagsToDocumentMode(ActivityRecord r, boolean launchSingleInstance, boolean launchSingleTask, int launchFlags) { if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && @@ -2972,6 +3111,11 @@ class ActivityStarter { return this; } + ActivityStarter setInTaskFragment(TaskFragment taskFragment) { + mRequest.inTaskFragment = taskFragment; + return this; + } + ActivityStarter setWaitResult(WaitResult result) { mRequest.waitResult = result; return this; @@ -3055,5 +3199,7 @@ class ActivityStarter { pw.print(mDoResume); pw.print(" mAddingToTask="); pw.println(mAddingToTask); + pw.print(" mInTaskFragment="); + pw.println(mInTaskFragment); } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 1759cdeb60d7..3150ccd86d8c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -30,6 +30,7 @@ import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; import android.os.Bundle; import android.os.IBinder; +import android.os.LocaleList; import android.os.RemoteException; import android.service.voice.IVoiceInteractionSession; import android.util.IntArray; @@ -595,11 +596,56 @@ public abstract class ActivityTaskManagerInternal { public abstract boolean isBaseOfLockedTask(String packageName); /** - * Create an interface to update configuration for an application. + * Creates an interface to update configuration for the calling application. */ public abstract PackageConfigurationUpdater createPackageConfigurationUpdater(); /** + * Creates an interface to update configuration for an arbitrary application specified by it's + * packageName and userId. + */ + public abstract PackageConfigurationUpdater createPackageConfigurationUpdater( + String packageName, int userId); + + /** + * Retrieves and returns the app-specific configuration for an arbitrary application specified + * by its packageName and userId. Returns null if no app-specific configuration has been set. + */ + @Nullable + public abstract PackageConfig getApplicationConfig(String packageName, + int userId); + + /** + * Holds app-specific configurations. + */ + public static class PackageConfig { + /** + * nightMode for the application, null if app-specific nightMode is not set. + */ + @Nullable + public final Integer mNightMode; + + /** + * {@link LocaleList} for the application, null if app-specific locales are not set. + */ + @Nullable + public final LocaleList mLocales; + + PackageConfig(Integer nightMode, LocaleList locales) { + mNightMode = nightMode; + mLocales = locales; + } + + /** + * Returns the string representation of the app-specific configuration. + */ + @Override + public String toString() { + return "PackageConfig: nightMode " + mNightMode + " locales " + mLocales; + } + } + + /** * An interface to update configuration for an application, and will persist override * configuration for this package. */ @@ -611,6 +657,14 @@ public abstract class ActivityTaskManagerInternal { PackageConfigurationUpdater setNightMode(int nightMode); /** + * Sets the app-specific locales for the application referenced by this updater. + * This setting is persisted and will overlay on top of the system locales for + * the said application. + * @return the current {@link PackageConfigurationUpdater} updated with the provided locale. + */ + PackageConfigurationUpdater setLocales(LocaleList locales); + + /** * Commit changes. */ void commit(); @@ -621,4 +675,15 @@ public abstract class ActivityTaskManagerInternal { */ public abstract boolean hasSystemAlertWindowPermission(int callingUid, int callingPid, String callingPackage); + + /** Called when the device is waking up */ + public abstract void notifyWakingUp(); + + /** + * Registers a callback which can intercept activity starts. + * @throws IllegalArgumentException if duplicate ids are provided + */ + public abstract void registerActivityStartInterceptor( + @ActivityInterceptorCallback.OrderedId int id, + ActivityInterceptorCallback callback); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index ca4c1257323a..8726f4ff5a12 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -64,6 +64,7 @@ import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.TRANSIT_NONE; +import static android.view.WindowManager.TRANSIT_WAKE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; @@ -91,6 +92,8 @@ import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.Scr import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage.PACKAGE; import static com.android.server.am.EventLogTags.writeBootProgressEnableScreen; import static com.android.server.am.EventLogTags.writeConfigurationChanged; +import static com.android.server.wm.ActivityInterceptorCallback.FIRST_ORDERED_ID; +import static com.android.server.wm.ActivityInterceptorCallback.LAST_ORDERED_ID; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH; @@ -457,6 +460,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** The controller for all operations related to locktask. */ private LockTaskController mLockTaskController; private ActivityStartController mActivityStartController; + private SparseArray<ActivityInterceptorCallback> mActivityInterceptorCallbacks = + new SparseArray<>(); PackageConfigPersister mPackageConfigPersister; boolean mSuppressResizeConfigChanges; @@ -499,7 +504,27 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { * Whether normal application switches are allowed; a call to {@link #stopAppSwitches() * disables this. */ - private volatile boolean mAppSwitchesAllowed = true; + private volatile int mAppSwitchesState = APP_SWITCH_ALLOW; + + // The duration of resuming foreground app switch from disallow. + private static final long RESUME_FG_APP_SWITCH_MS = 500; + + /** App switch is not allowed. */ + static final int APP_SWITCH_DISALLOW = 0; + + /** App switch is allowed only if the activity launch was requested by a foreground app. */ + static final int APP_SWITCH_FG_ONLY = 1; + + /** App switch is allowed. */ + static final int APP_SWITCH_ALLOW = 2; + + @IntDef({ + APP_SWITCH_DISALLOW, + APP_SWITCH_FG_ONLY, + APP_SWITCH_ALLOW, + }) + @Retention(RetentionPolicy.SOURCE) + @interface AppSwitchState {} /** * Last stop app switches time, apps finished before this time cannot start background activity @@ -652,16 +677,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { */ volatile int mTopProcessState = ActivityManager.PROCESS_STATE_TOP; + /** Whether to keep higher priority to launch app while device is sleeping. */ + private volatile boolean mRetainPowerModeAndTopProcessState; + + /** The timeout to restore power mode if {@link #mRetainPowerModeAndTopProcessState} is set. */ + private static final long POWER_MODE_UNKNOWN_VISIBILITY_TIMEOUT_MS = 1000; + @Retention(RetentionPolicy.SOURCE) @IntDef({ POWER_MODE_REASON_START_ACTIVITY, POWER_MODE_REASON_FREEZE_DISPLAY, + POWER_MODE_REASON_UNKNOWN_VISIBILITY, POWER_MODE_REASON_ALL, }) @interface PowerModeReason {} static final int POWER_MODE_REASON_START_ACTIVITY = 1 << 0; static final int POWER_MODE_REASON_FREEZE_DISPLAY = 1 << 1; + /** @see UnknownAppVisibilityController */ + static final int POWER_MODE_REASON_UNKNOWN_VISIBILITY = 1 << 2; /** This can only be used by {@link #endLaunchPowerMode(int)}.*/ static final int POWER_MODE_REASON_ALL = (1 << 2) - 1; @@ -732,6 +766,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { WindowOrganizerController mWindowOrganizerController; TaskOrganizerController mTaskOrganizerController; + TaskFragmentOrganizerController mTaskFragmentOrganizerController; @Nullable private BackgroundActivityStartCallback mBackgroundActivityStartCallback; @@ -805,6 +840,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", GL_ES_VERSION_UNDEFINED); mWindowOrganizerController = new WindowOrganizerController(this); mTaskOrganizerController = mWindowOrganizerController.mTaskOrganizerController; + mTaskFragmentOrganizerController = + mWindowOrganizerController.mTaskFragmentOrganizerController; } public void onSystemReady() { @@ -943,7 +980,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { setRecentTasks(new RecentTasks(this, mTaskSupervisor)); mVrController = new VrController(mGlobalLock); mKeyguardController = mTaskSupervisor.getKeyguardController(); - mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue); + mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue, this); } public void onActivityManagerInternalAdded() { @@ -974,6 +1011,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { synchronized (mGlobalLock) { mWindowManager = wm; mRootWindowContainer = wm.mRoot; + mWindowOrganizerController.setWindowManager(wm); mTempConfig.setToDefaults(); mTempConfig.setLocales(LocaleList.getDefault()); mConfigurationSeq = mTempConfig.seq = 1; @@ -1102,6 +1140,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mBackgroundActivityStartCallback; } + SparseArray<ActivityInterceptorCallback> getActivityInterceptorCallbacks() { + return mActivityInterceptorCallbacks; + } + private void start() { LocalServices.addService(ActivityTaskManagerInternal.class, mInternal); } @@ -1222,10 +1264,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // If this is coming from the currently resumed activity, it is // effectively saying that app switches are allowed at this point. final Task topFocusedRootTask = getTopDisplayFocusedRootTask(); - if (topFocusedRootTask != null && topFocusedRootTask.getResumedActivity() != null - && topFocusedRootTask.getResumedActivity().info.applicationInfo.uid + if (topFocusedRootTask != null && topFocusedRootTask.getTopResumedActivity() != null + && topFocusedRootTask.getTopResumedActivity().info.applicationInfo.uid == Binder.getCallingUid()) { - mAppSwitchesAllowed = true; + mAppSwitchesState = APP_SWITCH_ALLOW; } } return pir.sendInner(0, fillInIntent, resolvedType, allowlistToken, null, null, @@ -1537,7 +1579,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { sourceToken = resultTo; } - sourceRecord = mRootWindowContainer.isInAnyTask(sourceToken); + sourceRecord = ActivityRecord.isInAnyTask(sourceToken); if (sourceRecord == null) { throw new SecurityException("Called with bad activity token: " + sourceToken); } @@ -1719,10 +1761,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(bOptions); final long origId = Binder.clearCallingIdentity(); try { - synchronized (mGlobalLock) { - return mTaskSupervisor.startActivityFromRecents(callingPid, callingUid, taskId, - safeOptions); - } + return mTaskSupervisor.startActivityFromRecents(callingPid, callingUid, taskId, + safeOptions); } finally { Binder.restoreCallingIdentity(origId); } @@ -1881,25 +1921,42 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public void setFocusedTask(int taskId) { enforceTaskPermission("setFocusedTask()"); - ProtoLog.d(WM_DEBUG_FOCUS, "setFocusedTask: taskId=%d", taskId); final long callingId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - final Task task = mRootWindowContainer.anyTaskForId(taskId, - MATCH_ATTACHED_TASK_ONLY); - if (task == null) { - return; - } - final ActivityRecord r = task.topRunningActivityLocked(); - if (r != null && r.moveFocusableActivityToTop("setFocusedTask")) { - mRootWindowContainer.resumeFocusedTasksTopActivities(); - } + setFocusedTask(taskId, null /* touchedActivity */); } } finally { Binder.restoreCallingIdentity(callingId); } } + void setFocusedTask(int taskId, ActivityRecord touchedActivity) { + ProtoLog.d(WM_DEBUG_FOCUS, "setFocusedTask: taskId=%d touchedActivity=%s", taskId, + touchedActivity); + final Task task = mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_ONLY); + if (task == null) { + return; + } + final ActivityRecord r = task.topRunningActivityLocked(); + if (r == null) { + return; + } + + if (r.moveFocusableActivityToTop("setFocusedTask")) { + mRootWindowContainer.resumeFocusedTasksTopActivities(); + } else if (touchedActivity != null && touchedActivity.isFocusable()) { + final TaskFragment parent = touchedActivity.getTaskFragment(); + if (parent != null && parent.isEmbedded()) { + // Set the focused app directly if the focused window is currently embedded + final DisplayContent displayContent = touchedActivity.getDisplayContent(); + displayContent.setFocusedApp(touchedActivity); + mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, + true /* updateInputWindows */); + } + } + } + @Override public boolean removeTask(int taskId) { mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeTask()"); @@ -2121,8 +2178,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** * Return true if app switching is allowed. */ - boolean getBalAppSwitchesAllowed() { - return mAppSwitchesAllowed; + @AppSwitchState int getBalAppSwitchesState() { + return mAppSwitchesState; } /** Register an {@link AnrController} to control the ANR dialog behavior */ @@ -3431,7 +3488,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public IWindowOrganizerController getWindowOrganizerController() { - enforceTaskPermission("getWindowOrganizerController()"); return mWindowOrganizerController; } @@ -3644,8 +3700,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public void stopAppSwitches() { mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, "stopAppSwitches"); synchronized (mGlobalLock) { - mAppSwitchesAllowed = false; + mAppSwitchesState = APP_SWITCH_DISALLOW; mLastStopAppSwitchesTime = SystemClock.uptimeMillis(); + mH.removeMessages(H.RESUME_FG_APP_SWITCH_MSG); + mH.sendEmptyMessageDelayed(H.RESUME_FG_APP_SWITCH_MSG, RESUME_FG_APP_SWITCH_MS); } } @@ -3653,7 +3711,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public void resumeAppSwitches() { mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, "resumeAppSwitches"); synchronized (mGlobalLock) { - mAppSwitchesAllowed = true; + mAppSwitchesState = APP_SWITCH_ALLOW; + mH.removeMessages(H.RESUME_FG_APP_SWITCH_MSG); } } @@ -3775,6 +3834,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + @Override + public void detachNavigationBarFromApp(@NonNull IBinder transition) { + mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, + "detachNavigationBarFromApp"); + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + getTransitionController().legacyDetachNavigationBarFromApp(transition); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + void dumpLastANRLocked(PrintWriter pw) { pw.println("ACTIVITY MANAGER LAST ANR (dumpsys activity lastanr)"); if (mLastANRState == null) { @@ -4038,6 +4111,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { + // Window configuration is unrelated to persistent configuration (e.g. font scale, + // locale). Unset it to avoid affecting the current display configuration. + values.windowConfiguration.setToDefaults(); updateConfigurationLocked(values, null, false, true, userId, false /* deferResume */); } @@ -4217,15 +4293,39 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } void startLaunchPowerMode(@PowerModeReason int reason) { - if (mPowerManagerInternal == null) return; - mPowerManagerInternal.setPowerMode(Mode.LAUNCH, true); + if (mPowerManagerInternal != null) { + mPowerManagerInternal.setPowerMode(Mode.LAUNCH, true); + } mLaunchPowerModeReasons |= reason; + if ((reason & POWER_MODE_REASON_UNKNOWN_VISIBILITY) != 0) { + if (mRetainPowerModeAndTopProcessState) { + mH.removeMessages(H.END_POWER_MODE_UNKNOWN_VISIBILITY_MSG); + } + mRetainPowerModeAndTopProcessState = true; + mH.sendEmptyMessageDelayed(H.END_POWER_MODE_UNKNOWN_VISIBILITY_MSG, + POWER_MODE_UNKNOWN_VISIBILITY_TIMEOUT_MS); + Slog.d(TAG, "Temporarily retain top process state for launching app"); + } } void endLaunchPowerMode(@PowerModeReason int reason) { - if (mPowerManagerInternal == null || mLaunchPowerModeReasons == 0) return; + if (mLaunchPowerModeReasons == 0) return; mLaunchPowerModeReasons &= ~reason; - if (mLaunchPowerModeReasons == 0) { + + if ((mLaunchPowerModeReasons & POWER_MODE_REASON_UNKNOWN_VISIBILITY) != 0) { + boolean allResolved = true; + for (int i = mRootWindowContainer.getChildCount() - 1; i >= 0; i--) { + allResolved &= mRootWindowContainer.getChildAt(i).mUnknownAppVisibilityController + .allResolved(); + } + if (allResolved) { + mLaunchPowerModeReasons &= ~POWER_MODE_REASON_UNKNOWN_VISIBILITY; + mRetainPowerModeAndTopProcessState = false; + mH.removeMessages(H.END_POWER_MODE_UNKNOWN_VISIBILITY_MSG); + } + } + + if (mLaunchPowerModeReasons == 0 && mPowerManagerInternal != null) { mPowerManagerInternal.setPowerMode(Mode.LAUNCH, false); } } @@ -4676,7 +4776,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mContext.getText(R.string.heavy_weight_notification_detail)) .setContentIntent(PendingIntent.getActivityAsUser(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT - | PendingIntent.FLAG_IMMUTABLE, null, + | PendingIntent.FLAG_IMMUTABLE, null, new UserHandle(userId))) .build(); try { @@ -5061,9 +5161,39 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { process.registerDisplayAreaConfigurationListener(imeContainer); } + @Override + public void setRunningRemoteTransitionDelegate(IApplicationThread caller) { + mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, + "setRunningRemoteTransition"); + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + synchronized (mGlobalLock) { + // Also only allow a process which is already runningRemoteAnimation to mark another + // process. + final WindowProcessController callingProc = getProcessController(callingPid, + callingUid); + if (callingProc == null || !callingProc.isRunningRemoteTransition()) { + final String msg = "Can't call setRunningRemoteTransition from a process (pid=" + + callingPid + " uid=" + callingUid + ") which isn't itself running a " + + "remote transition."; + Slog.e(TAG, msg); + throw new SecurityException(msg); + } + final WindowProcessController wpc = getProcessController(caller); + if (wpc == null) { + Slog.w(TAG, "Unable to find process for application " + caller); + return; + } + wpc.setRunningRemoteAnimation(true /* running */); + callingProc.addRemoteAnimationDelegate(wpc); + } + } + final class H extends Handler { static final int REPORT_TIME_TRACKER_MSG = 1; static final int UPDATE_PROCESS_ANIMATING_STATE = 2; + static final int END_POWER_MODE_UNKNOWN_VISIBILITY_MSG = 3; + static final int RESUME_FG_APP_SWITCH_MSG = 4; static final int FIRST_ACTIVITY_TASK_MSG = 100; static final int FIRST_SUPERVISOR_TASK_MSG = 200; @@ -5087,6 +5217,28 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } break; + case END_POWER_MODE_UNKNOWN_VISIBILITY_MSG: { + synchronized (mGlobalLock) { + mRetainPowerModeAndTopProcessState = false; + endLaunchPowerMode(POWER_MODE_REASON_UNKNOWN_VISIBILITY); + if (mTopApp != null + && mTopProcessState == ActivityManager.PROCESS_STATE_TOP_SLEEPING) { + // Restore the scheduling group for sleeping. + mTopApp.updateProcessInfo(false /* updateServiceConnection */, + false /* activityChange */, true /* updateOomAdj */, + false /* addPendingTopUid */); + } + } + } + break; + case RESUME_FG_APP_SWITCH_MSG: { + synchronized (mGlobalLock) { + if (mAppSwitchesState == APP_SWITCH_DISALLOW) { + mAppSwitchesState = APP_SWITCH_FG_ONLY; + } + } + } + break; } } } @@ -5405,6 +5557,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @HotPath(caller = HotPath.OOM_ADJUSTMENT) @Override public int getTopProcessState() { + if (mRetainPowerModeAndTopProcessState) { + // There is a launching app while device may be sleeping, force the top state so + // the launching process can have top-app scheduling group. + return ActivityManager.PROCESS_STATE_TOP; + } return mTopProcessState; } @@ -6442,7 +6599,22 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public PackageConfigurationUpdater createPackageConfigurationUpdater() { - return new PackageConfigurationUpdaterImpl(Binder.getCallingPid()); + return new PackageConfigurationUpdaterImpl(Binder.getCallingPid(), + ActivityTaskManagerService.this); + } + + @Override + public PackageConfigurationUpdater createPackageConfigurationUpdater( + String packageName , int userId) { + return new PackageConfigurationUpdaterImpl(packageName, userId, + ActivityTaskManagerService.this); + } + + @Override + @Nullable + public ActivityTaskManagerInternal.PackageConfig getApplicationConfig(String packageName, + int userId) { + return mPackageConfigPersister.findPackageConfiguration(packageName, userId); } @Override @@ -6451,44 +6623,29 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return ActivityTaskManagerService.this.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage); } - } - - final class PackageConfigurationUpdaterImpl implements - ActivityTaskManagerInternal.PackageConfigurationUpdater { - private final int mPid; - private int mNightMode; - - PackageConfigurationUpdaterImpl(int pid) { - mPid = pid; - } @Override - public ActivityTaskManagerInternal.PackageConfigurationUpdater setNightMode(int nightMode) { - mNightMode = nightMode; - return this; + public void notifyWakingUp() { + // Start a transition for waking. This is needed for showWhenLocked activities. + getTransitionController().requestTransitionIfNeeded(TRANSIT_WAKE, 0 /* flags */, + null /* trigger */, mRootWindowContainer.getDefaultDisplay()); } @Override - public void commit() { + public void registerActivityStartInterceptor( + @ActivityInterceptorCallback.OrderedId int id, + ActivityInterceptorCallback callback) { synchronized (mGlobalLock) { - final long ident = Binder.clearCallingIdentity(); - try { - final WindowProcessController wpc = mProcessMap.getProcess(mPid); - if (wpc == null) { - Slog.w(TAG, "Override application configuration: cannot find pid " + mPid); - return; - } - wpc.setOverrideNightMode(mNightMode); - wpc.updateNightModeForAllActivities(mNightMode); - mPackageConfigPersister.updateFromImpl(wpc.mName, wpc.mUserId, this); - } finally { - Binder.restoreCallingIdentity(ident); + if (mActivityInterceptorCallbacks.contains(id)) { + throw new IllegalArgumentException("Duplicate id provided: " + id); + } + if (id > LAST_ORDERED_ID || id < FIRST_ORDERED_ID) { + throw new IllegalArgumentException( + "Provided id " + id + " is not in range of valid ids [" + + FIRST_ORDERED_ID + "," + LAST_ORDERED_ID + "]"); } + mActivityInterceptorCallbacks.put(id, callback); } } - - int getNightMode() { - return mNightMode; - } } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 92da025ebb49..6be88c7c8fa0 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.Manifest.permission.ACTIVITY_EMBEDDING; import static android.Manifest.permission.CAMERA; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; +import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS; import static android.Manifest.permission.START_ANY_ACTIVITY; import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED; import static android.app.ActivityManager.START_DELIVERED_TO_TOP; @@ -44,11 +45,14 @@ import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static android.os.Process.INVALID_UID; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS; +import static com.android.server.wm.ActivityRecord.State.DESTROYED; +import static com.android.server.wm.ActivityRecord.State.PAUSED; +import static com.android.server.wm.ActivityRecord.State.PAUSING; +import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_IDLE; @@ -73,9 +77,6 @@ import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_R import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; -import static com.android.server.wm.Task.ActivityState.DESTROYED; -import static com.android.server.wm.Task.ActivityState.PAUSED; -import static com.android.server.wm.Task.ActivityState.PAUSING; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK; import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT; import static com.android.server.wm.Task.TAG_CLEANUP; @@ -143,7 +144,6 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.ReferrerIntent; -import com.android.internal.os.TransferPipe; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.pooled.PooledConsumer; @@ -151,10 +151,10 @@ import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService; import com.android.server.am.UserState; +import com.android.server.utils.Slogf; import com.android.server.wm.ActivityMetricsLogger.LaunchingState; import java.io.FileDescriptor; -import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -369,6 +369,12 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { */ private int mVisibilityTransactionDepth; + /** + * Whether to the visibility updates that started from {@code RootWindowContainer} should be + * deferred. + */ + private boolean mDeferRootVisibilityUpdate; + private ActivityMetricsLogger mActivityMetricsLogger; /** Check if placing task or activity on specified display is allowed. */ @@ -883,6 +889,12 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { logIfTransactionTooLarge(r.intent, r.getSavedState()); + if (r.isEmbedded()) { + // Sending TaskFragmentInfo to client to ensure the info is updated before + // the activity creation. + mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent( + r.getOrganizedTaskFragment()); + } // Create activity launch transaction. final ClientTransaction clientTransaction = ClientTransaction.obtain( @@ -1363,8 +1375,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // us, we can now deliver. r.idle = true; - //Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout); - // Check if able to finish booting when device is booting and all resumed activities // are idle. if ((mService.isBooting() && mRootWindowContainer.allResumedActivitiesIdle()) @@ -1397,14 +1407,21 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // Atomically retrieve all of the other things to do. processStoppingAndFinishingActivities(r, processPausingActivities, "idle"); + if (DEBUG_IDLE) { + Slogf.i(TAG, "activityIdleInternal(): r=%s, booting=%b, mStartingUsers=%s", r, booting, + mStartingUsers); + } + if (!mStartingUsers.isEmpty()) { final ArrayList<UserState> startingUsers = new ArrayList<>(mStartingUsers); mStartingUsers.clear(); - - if (!booting) { + // TODO(b/190854171): remove the isHeadlessSystemUserMode() check on master + if (!booting || UserManager.isHeadlessSystemUserMode()) { // Complete user switch. for (int i = 0; i < startingUsers.size(); i++) { - mService.mAmInternal.finishUserSwitch(startingUsers.get(i)); + UserState userState = startingUsers.get(i); + Slogf.i(TAG, "finishing switch of user %d", userState.mHandle.getIdentifier()); + mService.mAmInternal.finishUserSwitch(userState); } } } @@ -1437,8 +1454,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { mUserLeaving = true; } - mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_TO_FRONT, - 0 /* flags */, task, options != null ? options.getRemoteTransition() : null); + task.mTransitionController.requestTransitionIfNeeded(TRANSIT_TO_FRONT, + 0 /* flags */, task, task /* readyGroupRef */, + options != null ? options.getRemoteTransition() : null); reason = reason + " findTaskToMoveToFront"; boolean reparented = false; if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) { @@ -1611,13 +1629,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // Prevent recursion. return; } - if (task.isVisible()) { - mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_CLOSE, task); - } else { - // Removing a non-visible task doesn't require a transition, but if there is one - // collecting, this should be a member just in case. - mService.getTransitionController().collect(task); - } + task.mTransitionController.requestCloseTransitionIfNeeded(task); task.mInRemoveTask = true; try { task.performClearTask(reason); @@ -1999,12 +2011,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { mHandler.obtainMessage(LAUNCH_TASK_BEHIND_COMPLETE, token).sendToTarget(); } - /** Checks whether the userid is a profile of the current user. */ - boolean isCurrentProfileLocked(int userId) { - if (userId == mRootWindowContainer.mCurrentUser) return true; - return mService.mAmInternal.isCurrentProfile(userId); - } - /** * Processes the activities to be stopped or destroyed. This should be called when the resumed * activities are idle or drawn. @@ -2018,7 +2024,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { final ActivityRecord s = mStoppingActivities.get(i); final boolean animating = s.isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS) - || mService.getTransitionController().inTransition(s); + || s.inTransition(); ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b " + "finishing=%s", s, s.nowVisible, animating, s.finishing); if (!animating || mService.mShuttingDown) { @@ -2073,6 +2079,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { void removeHistoryRecords(WindowProcessController app) { removeHistoryRecords(mStoppingActivities, app, "mStoppingActivities"); removeHistoryRecords(mFinishingActivities, app, "mFinishingActivities"); + removeHistoryRecords(mNoHistoryActivities, app, "mNoHistoryActivities"); } private void removeHistoryRecords(ArrayList<ActivityRecord> list, WindowProcessController app, @@ -2110,6 +2117,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { mWaitingActivityLaunched.get(i).dump(pw, prefix + " "); } } + pw.println(prefix + "mNoHistoryActivities=" + mNoHistoryActivities); pw.println(); } @@ -2134,76 +2142,14 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { static boolean dumpHistoryList(FileDescriptor fd, PrintWriter pw, List<ActivityRecord> list, String prefix, String label, boolean complete, boolean brief, boolean client, String dumpPackage, boolean needNL, Runnable header, Task lastTask) { - String innerPrefix = null; - String[] args = null; boolean printed = false; - for (int i=list.size()-1; i>=0; i--) { + for (int i = list.size() - 1; i >= 0; i--) { final ActivityRecord r = list.get(i); - if (dumpPackage != null && !dumpPackage.equals(r.packageName)) { - continue; - } - if (innerPrefix == null) { - innerPrefix = prefix + " "; - args = new String[0]; - } - printed = true; - final boolean full = !brief && (complete || !r.isInHistory()); - if (needNL) { - pw.println(""); - needNL = false; - } - if (header != null) { - header.run(); - header = null; - } - if (lastTask != r.getTask()) { - lastTask = r.getTask(); - pw.print(prefix); - pw.print(full ? "* " : " "); - pw.println(lastTask); - if (full) { - lastTask.dump(pw, prefix + " "); - } else if (complete) { - // Complete + brief == give a summary. Isn't that obvious?!? - if (lastTask.intent != null) { - pw.print(prefix); pw.print(" "); - pw.println(lastTask.intent.toInsecureString()); - } - } - } - pw.print(prefix); pw.print(full ? " * " : " "); pw.print(label); - pw.print(" #"); pw.print(i); pw.print(": "); - pw.println(r); - if (full) { - r.dump(pw, innerPrefix, true /* dumpAll */); - } else if (complete) { - // Complete + brief == give a summary. Isn't that obvious?!? - pw.print(innerPrefix); pw.println(r.intent.toInsecureString()); - if (r.app != null) { - pw.print(innerPrefix); pw.println(r.app); - } - } - if (client && r.attachedToProcess()) { - // flush anything that is already in the PrintWriter since the thread is going - // to write to the file descriptor directly - pw.flush(); - try { - TransferPipe tp = new TransferPipe(); - try { - r.app.getThread().dumpActivity( - tp.getWriteFd(), r.appToken, innerPrefix, args); - // Short timeout, since blocking here can deadlock with the application. - tp.go(fd, 2000); - } finally { - tp.kill(); - } - } catch (IOException e) { - pw.println(innerPrefix + "Failure while dumping the activity: " + e); - } catch (RemoteException e) { - pw.println(innerPrefix + "Got a RemoteException while dumping the activity"); - } - needNL = true; - } + ActivityRecord.dumpActivity(fd, pw, i, r, prefix, label, complete, brief, + client, dumpPackage, needNL, header, lastTask); + lastTask = r.getTask(); + header = null; + needNL = client && r.attachedToProcess(); } return printed; } @@ -2216,6 +2162,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { final void scheduleIdle() { if (!mHandler.hasMessages(IDLE_NOW_MSG)) { + if (DEBUG_IDLE) Slog.d(TAG_IDLE, "scheduleIdle: Callers=" + Debug.getCallers(4)); mHandler.sendEmptyMessage(IDLE_NOW_MSG); } } @@ -2230,7 +2177,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { void updateTopResumedActivityIfNeeded() { final ActivityRecord prevTopActivity = mTopResumedActivity; final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask(); - if (topRootTask == null || topRootTask.getResumedActivity() == prevTopActivity) { + if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) { if (mService.isSleepingLocked()) { // There won't be a next resumed activity. The top process should still be updated // according to the current top focused activity. @@ -2252,7 +2199,17 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } // Update the current top activity. - mTopResumedActivity = topRootTask.getResumedActivity(); + mTopResumedActivity = topRootTask.getTopResumedActivity(); + // Update process state if there is no activity state change (e.g. focus change between + // multi-window mode activities) to make sure that the current top has top oom-adj. + // If the previous top is null, there should be activity state change from it, Then the + // process state should also have been updated so no need to update again. + if (mTopResumedActivity != null && prevTopActivity != null) { + if (mTopResumedActivity.app != null) { + mTopResumedActivity.app.addToPendingTop(); + } + mService.updateOomAdj(); + } scheduleTopResumedActivityStateIfNeeded(); mService.updateTopApp(mTopResumedActivity); @@ -2379,7 +2336,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } if (!task.supportsSplitScreenWindowingMode() || forceNonResizable) { - if (mService.getTransitionController().getTransitionPlayer() != null) return; + if (task.mTransitionController.isShellTransitionsEnabled()) return; // Dismiss docked root task. If task appeared to be in docked root task but is not // resizable - we need to move it to top of fullscreen root task, otherwise it will // be covered. @@ -2492,6 +2449,14 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { return mVisibilityTransactionDepth > 0; } + void setDeferRootVisibilityUpdate(boolean deferUpdate) { + mDeferRootVisibilityUpdate = deferUpdate; + } + + boolean isRootVisibilityUpdateDeferred() { + return mDeferRootVisibilityUpdate; + } + /** * Called when the state or visibility of an attached activity is changed. * @@ -2559,8 +2524,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { String processName = null; int uid = 0; synchronized (mService.mGlobalLock) { - if (r.attachedToProcess() - && r.isState(Task.ActivityState.RESTARTING_PROCESS)) { + if (r.attachedToProcess() && r.isState(RESTARTING_PROCESS)) { processName = r.app.mName; uid = r.app.mUid; } @@ -2660,102 +2624,127 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } } + /** + * Start the given task from the recent tasks. Do not hold WM global lock when calling this + * method to avoid potential deadlock or permission deny by UriGrantsManager when resolving + * activity (see {@link ActivityStarter.Request#resolveActivity} and + * {@link com.android.server.am.ContentProviderHelper#checkContentProviderUriPermission}). + * + * @return The result code of starter. + */ int startActivityFromRecents(int callingPid, int callingUid, int taskId, SafeActivityOptions options) { - Task task = null; + final Task task; + final int taskCallingUid; final String callingPackage; final String callingFeatureId; final Intent intent; final int userId; - int activityType = ACTIVITY_TYPE_UNDEFINED; - int windowingMode = WINDOWING_MODE_UNDEFINED; final ActivityOptions activityOptions = options != null ? options.getOptions(this) : null; boolean moveHomeTaskForward = true; - if (activityOptions != null) { - activityType = activityOptions.getLaunchActivityType(); - windowingMode = activityOptions.getLaunchWindowingMode(); - if (activityOptions.freezeRecentTasksReordering() - && mRecentTasks.isCallerRecents(callingUid)) { - mRecentTasks.setFreezeTaskListReordering(); + synchronized (mService.mGlobalLock) { + int activityType = ACTIVITY_TYPE_UNDEFINED; + if (activityOptions != null) { + activityType = activityOptions.getLaunchActivityType(); + final int windowingMode = activityOptions.getLaunchWindowingMode(); + if (activityOptions.freezeRecentTasksReordering() + && mService.checkPermission(MANAGE_ACTIVITY_TASKS, callingPid, callingUid) + == PERMISSION_GRANTED) { + mRecentTasks.setFreezeTaskListReordering(); + } + if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + || activityOptions.getLaunchRootTask() != null) { + // Don't move home activity forward if we are launching into primary split or + // there is a launch root set. + moveHomeTaskForward = false; + } } - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || activityOptions.getLaunchRootTask() != null) { - // Don't move home activity forward if we are launching into primary split or there - // is a launch root set. - moveHomeTaskForward = false; + if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { + throw new IllegalArgumentException("startActivityFromRecents: Task " + + taskId + " can't be launch in the home/recents root task."); } - } - if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { - throw new IllegalArgumentException("startActivityFromRecents: Task " - + taskId + " can't be launch in the home/recents root task."); - } - mService.deferWindowLayout(); - try { - task = mRootWindowContainer.anyTaskForId(taskId, - MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, activityOptions, ON_TOP); - if (task == null) { - mWindowManager.executeAppTransition(); - throw new IllegalArgumentException( - "startActivityFromRecents: Task " + taskId + " not found."); - } - - if (moveHomeTaskForward) { - // We always want to return to the home activity instead of the recents activity - // from whatever is started from the recents activity, so move the home root task - // forward. - // TODO (b/115289124): Multi-display supports for recents. - mRootWindowContainer.getDefaultTaskDisplayArea().moveHomeRootTaskToFront( - "startActivityFromRecents"); - } - - // If the user must confirm credentials (e.g. when first launching a work app and the - // Work Challenge is present) let startActivityInPackage handle the intercepting. - if (!mService.mAmInternal.shouldConfirmCredentials(task.mUserId) - && task.getRootActivity() != null) { - final ActivityRecord targetActivity = task.getTopNonFinishingActivity(); - - mRootWindowContainer.startPowerModeLaunchIfNeeded( - true /* forceSend */, targetActivity); - final LaunchingState launchingState = - mActivityMetricsLogger.notifyActivityLaunching(task.intent); - try { - mService.moveTaskToFrontLocked(null /* appThread */, null /* callingPackage */, - task.mTaskId, 0, options); - // Apply options to prevent pendingOptions be taken when scheduling activity - // lifecycle transaction to make sure the override pending app transition will - // be applied immediately. - targetActivity.applyOptionsAnimation(); - } finally { - mActivityMetricsLogger.notifyActivityLaunched(launchingState, - START_TASK_TO_FRONT, false /* newActivityCreated */, targetActivity, - activityOptions); + boolean shouldStartActivity = false; + mService.deferWindowLayout(); + try { + task = mRootWindowContainer.anyTaskForId(taskId, + MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, activityOptions, ON_TOP); + if (task == null) { + mWindowManager.executeAppTransition(); + throw new IllegalArgumentException( + "startActivityFromRecents: Task " + taskId + " not found."); } - mService.getActivityStartController().postStartActivityProcessingForLastStarter( - task.getTopNonFinishingActivity(), ActivityManager.START_TASK_TO_FRONT, - task.getRootTask()); + if (moveHomeTaskForward) { + // We always want to return to the home activity instead of the recents + // activity from whatever is started from the recents activity, so move + // the home root task forward. + // TODO (b/115289124): Multi-display supports for recents. + mRootWindowContainer.getDefaultTaskDisplayArea().moveHomeRootTaskToFront( + "startActivityFromRecents"); + } - // As it doesn't go to ActivityStarter.executeRequest() path, we need to resume - // app switching here also. - mService.resumeAppSwitches(); + // If the user must confirm credentials (e.g. when first launching a work + // app and the Work Challenge is present) let startActivityInPackage handle + // the intercepting. + if (!mService.mAmInternal.shouldConfirmCredentials(task.mUserId) + && task.getRootActivity() != null) { + final ActivityRecord targetActivity = task.getTopNonFinishingActivity(); + + mRootWindowContainer.startPowerModeLaunchIfNeeded( + true /* forceSend */, targetActivity); + final LaunchingState launchingState = + mActivityMetricsLogger.notifyActivityLaunching(task.intent); + try { + mService.moveTaskToFrontLocked(null /* appThread */, + null /* callingPackage */, task.mTaskId, 0, options); + // Apply options to prevent pendingOptions be taken when scheduling + // activity lifecycle transaction to make sure the override pending app + // transition will be applied immediately. + targetActivity.applyOptionsAnimation(); + } finally { + mActivityMetricsLogger.notifyActivityLaunched(launchingState, + START_TASK_TO_FRONT, false /* newActivityCreated */, + targetActivity, activityOptions); + } - return ActivityManager.START_TASK_TO_FRONT; + mService.getActivityStartController().postStartActivityProcessingForLastStarter( + task.getTopNonFinishingActivity(), ActivityManager.START_TASK_TO_FRONT, + task.getRootTask()); + + // As it doesn't go to ActivityStarter.executeRequest() path, we need to resume + // app switching here also. + mService.resumeAppSwitches(); + return ActivityManager.START_TASK_TO_FRONT; + } + // The task is empty or needs to show the confirmation for credential. + shouldStartActivity = true; + } finally { + if (!shouldStartActivity) { + mService.continueWindowLayout(); + } } + taskCallingUid = task.mCallingUid; callingPackage = task.mCallingPackage; callingFeatureId = task.mCallingFeatureId; intent = task.intent; intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); userId = task.mUserId; - return mService.getActivityStartController().startActivityInPackage(task.mCallingUid, + } + // ActivityStarter will acquire the lock where the places need, so execute the request + // outside of the lock. + try { + return mService.getActivityStartController().startActivityInPackage(taskCallingUid, callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null, null, 0, 0, options, userId, task, "startActivityFromRecents", false /* validateIncomingUser */, null /* originatingPendingIntent */, false /* allowBackgroundActivityStart */); } finally { - mService.continueWindowLayout(); + synchronized (mService.mGlobalLock) { + mService.continueWindowLayout(); + } } } @@ -2778,7 +2767,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } boolean matches(ActivityRecord r) { - return mTargetComponent.equals(r.mActivityComponent) || mLaunchingState.contains(r); + if (!mLaunchingState.hasActiveTransitionInfo()) { + return mTargetComponent.equals(r.mActivityComponent); + } + return mLaunchingState.contains(r); } void dump(PrintWriter pw, String prefix) { diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java index 529c4f608743..5899a4e89ca7 100644 --- a/services/core/java/com/android/server/wm/AnimationAdapter.java +++ b/services/core/java/com/android/server/wm/AnimationAdapter.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import android.annotation.NonNull; import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -52,7 +53,7 @@ interface AnimationAdapter { * @param finishCallback The callback to be invoked when the animation has finished. */ void startAnimation(SurfaceControl animationLeash, Transaction t, @AnimationType int type, - OnAnimationFinishedCallback finishCallback); + @NonNull OnAnimationFinishedCallback finishCallback); /** * Called when the animation that was started with {@link #startAnimation} was cancelled by the diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java index 892db9c33dbd..c881864dff25 100644 --- a/services/core/java/com/android/server/wm/AnrController.java +++ b/services/core/java/com/android/server/wm/AnrController.java @@ -31,7 +31,6 @@ import android.util.SparseArray; import android.view.InputApplicationHandle; import com.android.server.am.ActivityManagerService; -import com.android.server.wm.EmbeddedWindowController.EmbeddedWindow; import java.io.File; import java.util.ArrayList; @@ -81,21 +80,19 @@ class AnrController { final boolean aboveSystem; final ActivityRecord activity; synchronized (mService.mGlobalLock) { - WindowState windowState = mService.mInputToWindowMap.get(inputToken); - if (windowState != null) { - pid = windowState.mSession.mPid; - activity = windowState.mActivityRecord; - Slog.i(TAG_WM, "ANR in " + windowState.mAttrs.getTitle() + ". Reason:" + reason); - } else { - EmbeddedWindow embeddedWindow = mService.mEmbeddedWindowController.get(inputToken); - if (embeddedWindow == null) { - Slog.e(TAG_WM, "Unknown token, dropping notifyConnectionUnresponsive request"); - return; - } - pid = embeddedWindow.mOwnerPid; - windowState = embeddedWindow.mHostWindowState; - activity = null; // Don't blame the host process, instead blame the embedded pid. + InputTarget target = mService.getInputTargetFromToken(inputToken); + if (target == null) { + Slog.e(TAG_WM, "Unknown token, dropping notifyConnectionUnresponsive request"); + return; } + + WindowState windowState = target.getWindowState(); + pid = target.getPid(); + // Blame the activity if the input token belongs to the window. If the target is + // embedded, then we will blame the pid instead. + activity = (windowState.mInputChannelToken == inputToken) + ? windowState.mActivityRecord : null; + Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + reason); aboveSystem = isWindowAboveSystem(windowState); dumpAnrStateLocked(activity, windowState, reason); } @@ -109,19 +106,12 @@ class AnrController { void notifyWindowResponsive(IBinder inputToken) { final int pid; synchronized (mService.mGlobalLock) { - WindowState windowState = mService.mInputToWindowMap.get(inputToken); - if (windowState != null) { - pid = windowState.mSession.mPid; - } else { - // Check if the token belongs to an embedded window. - EmbeddedWindow embeddedWindow = mService.mEmbeddedWindowController.get(inputToken); - if (embeddedWindow == null) { - Slog.e(TAG_WM, - "Unknown token, dropping notifyWindowConnectionResponsive request"); - return; - } - pid = embeddedWindow.mOwnerPid; + InputTarget target = mService.getInputTargetFromToken(inputToken); + if (target == null) { + Slog.e(TAG_WM, "Unknown token, dropping notifyWindowConnectionResponsive request"); + return; } + pid = target.getPid(); } mService.mAmInternal.inputDispatchingResumed(pid); } diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 7f0adcacc951..558939611905 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -35,10 +35,10 @@ import android.os.UserHandle; */ class AppTaskImpl extends IAppTask.Stub { private static final String TAG = "AppTaskImpl"; - private ActivityTaskManagerService mService; + private final ActivityTaskManagerService mService; - private int mTaskId; - private int mCallingUid; + private final int mTaskId; + private final int mCallingUid; public AppTaskImpl(ActivityTaskManagerService service, int taskId, int callingUid) { mService = service; @@ -113,9 +113,9 @@ class AppTaskImpl extends IAppTask.Stub { return; } } - mService.mTaskSupervisor.startActivityFromRecents(callingPid, - callingUid, mTaskId, null); } + mService.mTaskSupervisor.startActivityFromRecents(callingPid, callingUid, mTaskId, + null /* options */); } finally { Binder.restoreCallingIdentity(origId); } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index c61cfeeac917..421a1c916fdc 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -40,6 +40,9 @@ import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_OLD_NONE; import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN; import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK; @@ -78,10 +81,7 @@ import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpe import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation; import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation; import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation; -import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN; -import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_ENTER_SCALE_UP; -import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN; -import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_EXIT_SCALE_UP; +import static com.android.internal.policy.TransitionAnimation.prepareThumbnailAnimationWithDuration; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE; @@ -102,12 +102,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Picture; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.hardware.HardwareBuffer; import android.os.Binder; import android.os.Debug; @@ -132,7 +127,6 @@ import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; -import android.view.animation.ClipRectAnimation; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import android.view.animation.ScaleAnimation; @@ -222,6 +216,7 @@ public class AppTransition implements Dump { private int mNextAppTransitionEnter; private int mNextAppTransitionExit; private int mNextAppTransitionInPlace; + private boolean mNextAppTransitionIsSync; // Keyed by WindowContainer hashCode. private final SparseArray<AppTransitionAnimationSpec> mNextAppTransitionAnimationsSpecs @@ -357,6 +352,13 @@ public class AppTransition implements Dump { fetchAppTransitionSpecsFromFuture(); } + void abort() { + if (mRemoteAnimationController != null) { + mRemoteAnimationController.cancelAnimation("aborted"); + } + clear(); + } + boolean isRunning() { return mAppTransitionState == APP_STATE_RUNNING; } @@ -445,6 +447,7 @@ public class AppTransition implements Dump { int redoLayout = notifyAppTransitionStartingLocked( AppTransition.isKeyguardGoingAwayTransitOld(transit), + AppTransition.isKeyguardOccludeTransitOld(transit), topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0, topOpeningAnim != null ? topOpeningAnim.getStatusBarTransitionsStartTime() @@ -479,6 +482,8 @@ public class AppTransition implements Dump { mNextAppTransitionAnimationsSpecsFuture = null; mDefaultNextAppTransitionAnimationSpec = null; mAnimationFinishedCallback = null; + mOverrideTaskTransition = false; + mNextAppTransitionIsSync = false; } void freeze() { @@ -557,12 +562,14 @@ public class AppTransition implements Dump { } } - private int notifyAppTransitionStartingLocked(boolean keyguardGoingAway, long duration, - long statusBarAnimationStartTime, long statusBarAnimationDuration) { + private int notifyAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOcclude, long duration, long statusBarAnimationStartTime, + long statusBarAnimationDuration) { int redoLayout = 0; for (int i = 0; i < mListeners.size(); i++) { redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway, - duration, statusBarAnimationStartTime, statusBarAnimationDuration); + keyguardOcclude, duration, statusBarAnimationStartTime, + statusBarAnimationDuration); } return redoLayout; } @@ -645,24 +652,6 @@ public class AppTransition implements Dump { /** * Prepares the specified animation with a standard duration, interpolator, etc. */ - Animation prepareThumbnailAnimationWithDuration(@Nullable Animation a, int appWidth, - int appHeight, long duration, Interpolator interpolator) { - if (a != null) { - if (duration > 0) { - a.setDuration(duration); - } - a.setFillAfter(true); - if (interpolator != null) { - a.setInterpolator(interpolator); - } - a.initialize(appWidth, appHeight, appWidth, appHeight); - } - return a; - } - - /** - * Prepares the specified animation with a standard duration, interpolator, etc. - */ Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight, int transit) { // Pick the desired duration. If this is an inter-activity transition, // it is the standard duration for that. Otherwise we use the longer @@ -682,56 +671,16 @@ public class AppTransition implements Dump { } /** - * Return the current thumbnail transition state. - */ - int getThumbnailTransitionState(boolean enter) { - if (enter) { - if (mNextAppTransitionScaleUp) { - return THUMBNAIL_TRANSITION_ENTER_SCALE_UP; - } else { - return THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN; - } - } else { - if (mNextAppTransitionScaleUp) { - return THUMBNAIL_TRANSITION_EXIT_SCALE_UP; - } else { - return THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN; - } - } - } - - /** * Creates an overlay with a background color and a thumbnail for the cross profile apps * animation. */ HardwareBuffer createCrossProfileAppsThumbnail( @DrawableRes int thumbnailDrawableRes, Rect frame) { - final int width = frame.width(); - final int height = frame.height(); - - final Picture picture = new Picture(); - final Canvas canvas = picture.beginRecording(width, height); - canvas.drawColor(Color.argb(0.6f, 0, 0, 0)); - final int thumbnailSize = mService.mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.cross_profile_apps_thumbnail_size); - final Drawable drawable = mService.mContext.getDrawable(thumbnailDrawableRes); - drawable.setBounds( - (width - thumbnailSize) / 2, - (height - thumbnailSize) / 2, - (width + thumbnailSize) / 2, - (height + thumbnailSize) / 2); - drawable.setTint(mContext.getColor(android.R.color.white)); - drawable.draw(canvas); - picture.endRecording(); - - return Bitmap.createBitmap(picture).getHardwareBuffer(); + return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame); } Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) { - final Animation animation = - mTransitionAnimation.loadCrossProfileAppThumbnailEnterAnimation(); - return prepareThumbnailAnimationWithDuration(animation, appRect.width(), - appRect.height(), 0, null); + return mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(appRect); } /** @@ -739,115 +688,14 @@ public class AppTransition implements Dump { * when a thumbnail is specified with the pending animation override. */ Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, @Nullable Rect contentInsets, - HardwareBuffer thumbnailHeader, WindowContainer container, int uiMode, - int orientation) { - Animation a; - final int thumbWidthI = thumbnailHeader.getWidth(); - final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; - final int thumbHeightI = thumbnailHeader.getHeight(); - final int appWidth = appRect.width(); - - float scaleW = appWidth / thumbWidth; - getNextAppTransitionStartRect(container, mTmpRect); - final float fromX; - float fromY; - final float toX; - float toY; - final float pivotX; - final float pivotY; - if (shouldScaleDownThumbnailTransition(uiMode, orientation)) { - fromX = mTmpRect.left; - fromY = mTmpRect.top; - - // For the curved translate animation to work, the pivot points needs to be at the - // same absolute position as the one from the real surface. - toX = mTmpRect.width() / 2 * (scaleW - 1f) + appRect.left; - toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top; - pivotX = mTmpRect.width() / 2; - pivotY = appRect.height() / 2 / scaleW; - if (mGridLayoutRecentsEnabled) { - // In the grid layout, the header is displayed above the thumbnail instead of - // overlapping it. - fromY -= thumbHeightI; - toY -= thumbHeightI * scaleW; - } - } else { - pivotX = 0; - pivotY = 0; - fromX = mTmpRect.left; - fromY = mTmpRect.top; - toX = appRect.left; - toY = appRect.top; - } - final long duration = getAspectScaleDuration(); - final Interpolator interpolator = getAspectScaleInterpolator(); - if (mNextAppTransitionScaleUp) { - // Animation up from the thumbnail to the full screen - Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, pivotX, pivotY); - scale.setInterpolator(interpolator); - scale.setDuration(duration); - Animation alpha = new AlphaAnimation(1f, 0f); - alpha.setInterpolator(mThumbnailFadeOutInterpolator); - alpha.setDuration(duration); - Animation translate = createCurvedMotion(fromX, toX, fromY, toY); - translate.setInterpolator(interpolator); - translate.setDuration(duration); - - mTmpFromClipRect.set(0, 0, thumbWidthI, thumbHeightI); - mTmpToClipRect.set(appRect); - - // Containing frame is in screen space, but we need the clip rect in the - // app space. - mTmpToClipRect.offsetTo(0, 0); - mTmpToClipRect.right = (int) (mTmpToClipRect.right / scaleW); - mTmpToClipRect.bottom = (int) (mTmpToClipRect.bottom / scaleW); - - if (contentInsets != null) { - mTmpToClipRect.inset((int) (-contentInsets.left * scaleW), - (int) (-contentInsets.top * scaleW), - (int) (-contentInsets.right * scaleW), - (int) (-contentInsets.bottom * scaleW)); - } - - Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect); - clipAnim.setInterpolator(interpolator); - clipAnim.setDuration(duration); - - // This AnimationSet uses the Interpolators assigned above. - AnimationSet set = new AnimationSet(false); - set.addAnimation(scale); - if (!mGridLayoutRecentsEnabled) { - // In the grid layout, the header should be shown for the whole animation. - set.addAnimation(alpha); - } - set.addAnimation(translate); - set.addAnimation(clipAnim); - a = set; - } else { - // Animation down from the full screen to the thumbnail - Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, pivotX, pivotY); - scale.setInterpolator(interpolator); - scale.setDuration(duration); - Animation alpha = new AlphaAnimation(0f, 1f); - alpha.setInterpolator(mThumbnailFadeInInterpolator); - alpha.setDuration(duration); - Animation translate = createCurvedMotion(toX, fromX, toY, fromY); - translate.setInterpolator(interpolator); - translate.setDuration(duration); - - // This AnimationSet uses the Interpolators assigned above. - AnimationSet set = new AnimationSet(false); - set.addAnimation(scale); - if (!mGridLayoutRecentsEnabled) { - // In the grid layout, the header should be shown for the whole animation. - set.addAnimation(alpha); - } - set.addAnimation(translate); - a = set; - - } - return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0, - null); + HardwareBuffer thumbnailHeader, WindowContainer container, int orientation) { + AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get( + container.hashCode()); + return mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(appRect, + contentInsets, thumbnailHeader, orientation, spec != null ? spec.rect : null, + mDefaultNextAppTransitionAnimationSpec != null + ? mDefaultNextAppTransitionAnimationSpec.rect : null, + mNextAppTransitionScaleUp); } private Animation createCurvedMotion(float fromX, float toX, float fromY, float toY) { @@ -1047,7 +895,7 @@ public class AppTransition implements Dump { + "transit=%s Callers=%s", a, appTransitionOldToString(transit), Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) { - a = mTransitionAnimation.createClipRevealAnimationLocked( + a = mTransitionAnimation.createClipRevealAnimationLockedCompat( transit, enter, frame, displayFrame, mDefaultNextAppTransitionAnimationSpec != null ? mDefaultNextAppTransitionAnimationSpec.rect : null); @@ -1056,7 +904,7 @@ public class AppTransition implements Dump { + "transit=%s Callers=%s", a, appTransitionOldToString(transit), Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) { - a = mTransitionAnimation.createScaleUpAnimationLocked(transit, enter, frame, + a = mTransitionAnimation.createScaleUpAnimationLockedCompat(transit, enter, frame, mDefaultNextAppTransitionAnimationSpec != null ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, @@ -1068,8 +916,8 @@ public class AppTransition implements Dump { mNextAppTransitionScaleUp = (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP); final HardwareBuffer thumbnailHeader = getAppTransitionThumbnailHeader(container); - a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked( - getThumbnailTransitionState(enter), frame, transit, thumbnailHeader, + a = mTransitionAnimation.createThumbnailEnterExitAnimationLockedCompat(enter, + mNextAppTransitionScaleUp, frame, transit, thumbnailHeader, mDefaultNextAppTransitionAnimationSpec != null ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, @@ -1084,9 +932,9 @@ public class AppTransition implements Dump { (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP); AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get( container.hashCode()); - a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked( - getThumbnailTransitionState(enter), orientation, transit, frame, - insets, surfaceInsets, stableInsets, freeform, spec != null ? spec.rect : null, + a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked(enter, + mNextAppTransitionScaleUp, orientation, transit, frame, insets, surfaceInsets, + stableInsets, freeform, spec != null ? spec.rect : null, mDefaultNextAppTransitionAnimationSpec != null ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, @@ -1102,7 +950,7 @@ public class AppTransition implements Dump { "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS: " + "anim=%s transit=%s isEntrance=true Callers=%s", a, appTransitionOldToString(transit), Debug.getCallers(3)); - } else if (transit == TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE) { + } else if (isChangeTransitOld(transit)) { // In the absence of a specific adapter, we just want to keep everything stationary. a = new AlphaAnimation(1.f, 1.f); a.setDuration(WindowChangeAnimationSpec.ANIMATION_DURATION); @@ -1168,6 +1016,21 @@ public class AppTransition implements Dump { animAttr = enter ? WindowAnimation_launchTaskBehindSourceAnimation : WindowAnimation_launchTaskBehindTargetAnimation; + break; + // TODO(b/189386466): Use activity transition as the fallback. Investigate if we + // need new TaskFragment transition. + case TRANSIT_OLD_TASK_FRAGMENT_OPEN: + animAttr = enter + ? WindowAnimation_activityOpenEnterAnimation + : WindowAnimation_activityOpenExitAnimation; + break; + // TODO(b/189386466): Use activity transition as the fallback. Investigate if we + // need new TaskFragment transition. + case TRANSIT_OLD_TASK_FRAGMENT_CLOSE: + animAttr = enter + ? WindowAnimation_activityCloseEnterAnimation + : WindowAnimation_activityCloseExitAnimation; + break; } a = animAttr != 0 ? loadAnimationAttr(lp, animAttr, transit) : null; ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, @@ -1318,13 +1181,19 @@ public class AppTransition implements Dump { } void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter) { + overridePendingAppTransitionRemote(remoteAnimationAdapter, false /* sync */); + } + + void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter, + boolean sync) { ProtoLog.i(WM_DEBUG_APP_TRANSITIONS, "Override pending remote transitionSet=%b adapter=%s", isTransitionSet(), remoteAnimationAdapter); - if (isTransitionSet()) { + if (isTransitionSet() && !mNextAppTransitionIsSync) { clear(); mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE; mRemoteAnimationController = new RemoteAnimationController(mService, mDisplayContent, remoteAnimationAdapter, mHandler); + mNextAppTransitionIsSync = sync; } } @@ -1472,6 +1341,15 @@ public class AppTransition implements Dump { case TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE: { return "TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE"; } + case TRANSIT_OLD_TASK_FRAGMENT_OPEN: { + return "TRANSIT_OLD_TASK_FRAGMENT_OPEN"; + } + case TRANSIT_OLD_TASK_FRAGMENT_CLOSE: { + return "TRANSIT_OLD_TASK_FRAGMENT_CLOSE"; + } + case TRANSIT_OLD_TASK_FRAGMENT_CHANGE: { + return "TRANSIT_OLD_TASK_FRAGMENT_CHANGE"; + } default: { return "<UNKNOWN: " + transition + ">"; } @@ -1676,7 +1554,7 @@ public class AppTransition implements Dump { } boolean prepareAppTransition(@TransitionType int transit, @TransitionFlags int flags) { - if (mService.mAtmService.getTransitionController().getTransitionPlayer() != null) { + if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) { return false; } mNextAppTransitionRequests.add(transit); @@ -1729,7 +1607,8 @@ public class AppTransition implements Dump { } static boolean isChangeTransitOld(@TransitionOldType int transit) { - return transit == TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; + return transit == TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE + || transit == TRANSIT_OLD_TASK_FRAGMENT_CHANGE; } static boolean isClosingTransitOld(@TransitionOldType int transit) { @@ -1755,21 +1634,21 @@ public class AppTransition implements Dump { } @TransitionType int getKeyguardTransition() { + if (mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_GOING_AWAY) != -1) { + return TRANSIT_KEYGUARD_GOING_AWAY; + } + final int unoccludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE); + final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE); + // No keyguard related transition requests. + if (unoccludeIndex == -1 && occludeIndex == -1) { + return TRANSIT_NONE; + } // In case we unocclude Keyguard and occlude it again, meaning that we never actually // unoccclude/occlude Keyguard, but just run a normal transition. - final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE); - if (occludeIndex != -1 - && occludeIndex < mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE)) { + if (unoccludeIndex != -1 && unoccludeIndex < occludeIndex) { return TRANSIT_NONE; } - - for (int i = 0; i < mNextAppTransitionRequests.size(); ++i) { - final @TransitionType int transit = mNextAppTransitionRequests.get(i); - if (isKeyguardTransit(transit)) { - return transit; - } - } - return TRANSIT_NONE; + return unoccludeIndex != -1 ? TRANSIT_KEYGUARD_UNOCCLUDE : TRANSIT_KEYGUARD_OCCLUDE; } @TransitionType int getFirstAppTransition() { diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index c869ec67776d..721907c21904 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -39,6 +39,9 @@ import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_OLD_NONE; import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN; import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK; @@ -62,12 +65,16 @@ import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_S import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN; import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN; import static com.android.server.wm.AppTransition.isNormalTransit; +import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp; +import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; +import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import android.annotation.IntDef; import android.annotation.Nullable; import android.os.Trace; import android.util.ArrayMap; @@ -82,10 +89,13 @@ import android.view.WindowManager.TransitionFlags; import android.view.WindowManager.TransitionOldType; import android.view.WindowManager.TransitionType; import android.view.animation.Animation; +import android.window.ITaskFragmentOrganizer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.LinkedList; import java.util.function.Predicate; @@ -102,7 +112,22 @@ public class AppTransitionController { private RemoteAnimationDefinition mRemoteAnimationDefinition = null; private static final int KEYGUARD_GOING_AWAY_ANIMATION_DURATION = 400; + private static final int TYPE_NONE = 0; + private static final int TYPE_ACTIVITY = 1; + private static final int TYPE_TASK_FRAGMENT = 2; + private static final int TYPE_TASK = 3; + + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_NONE, + TYPE_ACTIVITY, + TYPE_TASK_FRAGMENT, + TYPE_TASK + }) + @Retention(RetentionPolicy.SOURCE) + @interface TransitContainerType {} + private final ArrayMap<WindowContainer, Integer> mTempTransitionReasons = new ArrayMap<>(); + private final ArrayList<WindowContainer> mTempTransitionWindows = new ArrayList<>(); AppTransitionController(WindowManagerService service, DisplayContent displayContent) { mService = service; @@ -144,13 +169,16 @@ public class AppTransitionController { void handleAppTransitionReady() { mTempTransitionReasons.clear(); if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons) - || !transitionGoodToGo(mDisplayContent.mChangingContainers, - mTempTransitionReasons)) { + || !transitionGoodToGo(mDisplayContent.mChangingContainers, mTempTransitionReasons) + || !transitionGoodToGoForTaskFragments()) { return; } Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady"); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO"); + // TODO(b/205335975): Remove window which stuck in animatingExit status. Find actual cause. + mDisplayContent.forAllWindows(WindowState::cleanupAnimatingExitWindow, + true /* traverseTopToBottom */); // TODO(new-app-transition): Remove code using appTransition.getAppTransition() final AppTransition appTransition = mDisplayContent.mAppTransition; @@ -185,8 +213,8 @@ public class AppTransitionController { mDisplayContent.mOpeningApps); final @TransitionOldType int transit = getTransitCompatType( - mDisplayContent.mAppTransition, - mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, + mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps, + mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers, mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(), mDisplayContent.mSkipAppTransitionAnimation); mDisplayContent.mSkipAppTransitionAnimation = false; @@ -213,7 +241,13 @@ public class AppTransitionController { final ActivityRecord topChangingApp = getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */); final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity); - overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes); + + // Check if there is any override + if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) { + // Unfreeze the windows that were previously frozen for TaskFragment animation. + unfreezeEmbeddedChangingWindows(); + overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes); + } final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mOpeningApps) || containsVoiceInteraction(mDisplayContent.mOpeningApps); @@ -267,6 +301,7 @@ public class AppTransitionController { * @param appTransition {@link AppTransition} for managing app transition state. * @param openingApps {@link ActivityRecord}s which are becoming visible. * @param closingApps {@link ActivityRecord}s which are becoming invisible. + * @param changingContainers {@link WindowContainer}s which are changed in configuration. * @param wallpaperTarget If non-null, this is the currently visible window that is associated * with the wallpaper. * @param oldWallpaper The currently visible window that is associated with the wallpaper in @@ -275,8 +310,8 @@ public class AppTransitionController { */ static @TransitionOldType int getTransitCompatType(AppTransition appTransition, ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps, - @Nullable WindowState wallpaperTarget, @Nullable WindowState oldWallpaper, - boolean skipAppTransitionAnimation) { + ArraySet<WindowContainer> changingContainers, @Nullable WindowState wallpaperTarget, + @Nullable WindowState oldWallpaper, boolean skipAppTransitionAnimation) { // Determine if closing and opening app token sets are wallpaper targets, in which case // special animations are needed. @@ -309,8 +344,18 @@ public class AppTransitionController { // Special transitions // TODO(new-app-transitions): Revisit if those can be rewritten by using flags. - if (appTransition.containsTransitRequest(TRANSIT_CHANGE)) { - return TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; + if (appTransition.containsTransitRequest(TRANSIT_CHANGE) && !changingContainers.isEmpty()) { + @TransitContainerType int changingType = + getTransitContainerType(changingContainers.valueAt(0)); + switch (changingType) { + case TYPE_TASK: + return TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; + case TYPE_TASK_FRAGMENT: + return TRANSIT_OLD_TASK_FRAGMENT_CHANGE; + default: + throw new IllegalStateException( + "TRANSIT_CHANGE with unrecognized changing type=" + changingType); + } } if ((flags & TRANSIT_FLAG_APP_CRASHED) != 0) { return TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE; @@ -387,33 +432,38 @@ public class AppTransitionController { openingApps, closingApps, true /* visible */); final ArraySet<WindowContainer> closingWcs = getAnimationTargets( openingApps, closingApps, false /* visible */); - final boolean isActivityOpening = !openingWcs.isEmpty() - && openingWcs.valueAt(0).asActivityRecord() != null; - final boolean isActivityClosing = !closingWcs.isEmpty() - && closingWcs.valueAt(0).asActivityRecord() != null; - final boolean isTaskOpening = !openingWcs.isEmpty() && !isActivityOpening; - final boolean isTaskClosing = !closingWcs.isEmpty() && !isActivityClosing; - - if (appTransition.containsTransitRequest(TRANSIT_TO_FRONT) && isTaskOpening) { + final WindowContainer<?> openingContainer = !openingWcs.isEmpty() + ? openingWcs.valueAt(0) : null; + final WindowContainer<?> closingContainer = !closingWcs.isEmpty() + ? closingWcs.valueAt(0) : null; + @TransitContainerType int openingType = getTransitContainerType(openingContainer); + @TransitContainerType int closingType = getTransitContainerType(closingContainer); + if (appTransition.containsTransitRequest(TRANSIT_TO_FRONT) && openingType == TYPE_TASK) { return TRANSIT_OLD_TASK_TO_FRONT; } - if (appTransition.containsTransitRequest(TRANSIT_TO_BACK) && isTaskClosing) { + if (appTransition.containsTransitRequest(TRANSIT_TO_BACK) && closingType == TYPE_TASK) { return TRANSIT_OLD_TASK_TO_BACK; } if (appTransition.containsTransitRequest(TRANSIT_OPEN)) { - if (isTaskOpening) { + if (openingType == TYPE_TASK) { return (appTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0 ? TRANSIT_OLD_TASK_OPEN_BEHIND : TRANSIT_OLD_TASK_OPEN; } - if (isActivityOpening) { + if (openingType == TYPE_ACTIVITY) { return TRANSIT_OLD_ACTIVITY_OPEN; } + if (openingType == TYPE_TASK_FRAGMENT) { + return TRANSIT_OLD_TASK_FRAGMENT_OPEN; + } } if (appTransition.containsTransitRequest(TRANSIT_CLOSE)) { - if (isTaskClosing) { + if (closingType == TYPE_TASK) { return TRANSIT_OLD_TASK_CLOSE; } - if (isActivityClosing) { + if (closingType == TYPE_TASK_FRAGMENT) { + return TRANSIT_OLD_TASK_FRAGMENT_CLOSE; + } + if (closingType == TYPE_ACTIVITY) { for (int i = closingApps.size() - 1; i >= 0; i--) { if (closingApps.valueAt(i).visibleIgnoringKeyguard) { return TRANSIT_OLD_ACTIVITY_CLOSE; @@ -430,6 +480,24 @@ public class AppTransitionController { return TRANSIT_OLD_NONE; } + @TransitContainerType + private static int getTransitContainerType(@Nullable WindowContainer<?> container) { + if (container == null) { + return TYPE_NONE; + } + if (container.asTask() != null) { + return TYPE_TASK; + } + if (container.asTaskFragment() != null) { + return TYPE_TASK_FRAGMENT; + } + if (container.asActivityRecord() != null) { + return TYPE_ACTIVITY; + } + return TYPE_NONE; + } + + @Nullable private static WindowManager.LayoutParams getAnimLp(ActivityRecord activity) { final WindowState mainWindow = activity != null ? activity.findMainWindow() : null; return mainWindow != null ? mainWindow.mAttrs : null; @@ -452,6 +520,142 @@ public class AppTransitionController { : null; } + private void unfreezeEmbeddedChangingWindows() { + final ArraySet<WindowContainer> changingContainers = mDisplayContent.mChangingContainers; + for (int i = changingContainers.size() - 1; i >= 0; i--) { + final WindowContainer wc = changingContainers.valueAt(i); + if (wc.isEmbedded()) { + wc.mSurfaceFreezer.unfreeze(wc.getSyncTransaction()); + } + } + } + + private boolean transitionMayContainNonAppWindows(@TransitionOldType int transit) { + // We don't want to have the client to animate any non-app windows. + // Having {@code transit} of those types doesn't mean it will contain non-app windows, but + // non-app windows will only be included with those transition types. And we don't currently + // have any use case of those for TaskFragment transition. + return shouldStartNonAppWindowAnimationsForKeyguardExit(transit) + || shouldAttachNavBarToApp(mService, mDisplayContent, transit) + || shouldStartWallpaperAnimation(mDisplayContent); + } + + /** + * Finds the common {@link android.window.TaskFragmentOrganizer} that organizes all app windows + * in the current transition. + * @return {@code null} if there is no such organizer, or if there are more than one. + */ + @Nullable + private ITaskFragmentOrganizer findTaskFragmentOrganizerForAllWindows() { + mTempTransitionWindows.clear(); + mTempTransitionWindows.addAll(mDisplayContent.mClosingApps); + mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps); + mTempTransitionWindows.addAll(mDisplayContent.mChangingContainers); + + // It should only animated by the organizer if all windows are below the same leaf Task. + Task leafTask = null; + for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) { + final ActivityRecord r = getAppFromContainer(mTempTransitionWindows.get(i)); + if (r == null) { + leafTask = null; + break; + } + // The activity may be a child of embedded Task, but we want to find the owner Task. + // As a result, find the organized TaskFragment first. + final TaskFragment organizedTaskFragment = r.getOrganizedTaskFragment(); + // There are also cases where the Task contains non-embedded activity, such as launching + // split TaskFragments from a non-embedded activity. + // The hierarchy may looks like this: + // - Task + // - Activity + // - TaskFragment + // - Activity + // - TaskFragment + // - Activity + // We also want to have the organizer handle the transition for such case. + final Task task = organizedTaskFragment != null + ? organizedTaskFragment.getTask() + : r.getTask(); + if (task == null) { + leafTask = null; + break; + } + // We don't want the organizer to handle transition of other non-embedded Task. + if (leafTask != null && leafTask != task) { + leafTask = null; + break; + } + final ActivityRecord rootActivity = task.getRootActivity(); + // We don't want the organizer to handle transition when the whole app is closing. + if (rootActivity == null) { + leafTask = null; + break; + } + // We don't want the organizer to handle transition of non-embedded activity of other + // app. + if (r.getUid() != task.effectiveUid && !r.isEmbedded()) { + leafTask = null; + break; + } + leafTask = task; + } + mTempTransitionWindows.clear(); + if (leafTask == null) { + return null; + } + + // We don't support remote animation for Task with multiple TaskFragmentOrganizers. + final ITaskFragmentOrganizer[] organizer = new ITaskFragmentOrganizer[1]; + final boolean hasMultipleOrganizers = leafTask.forAllLeafTaskFragments(taskFragment -> { + final ITaskFragmentOrganizer tfOrganizer = taskFragment.getTaskFragmentOrganizer(); + if (tfOrganizer == null) { + return false; + } + if (organizer[0] != null && !organizer[0].asBinder().equals(tfOrganizer.asBinder())) { + return true; + } + organizer[0] = tfOrganizer; + return false; + }); + if (hasMultipleOrganizers) { + ProtoLog.e(WM_DEBUG_APP_TRANSITIONS, "We don't support remote animation for" + + " Task with multiple TaskFragmentOrganizers."); + return null; + } + return organizer[0]; + } + + /** + * Overrides the pending transition with the remote animation defined by the + * {@link ITaskFragmentOrganizer} if all windows in the transition are children of + * {@link TaskFragment} that are organized by the same organizer. + * + * @return {@code true} if the transition is overridden. + */ + private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit, + ArraySet<Integer> activityTypes) { + if (transitionMayContainNonAppWindows(transit)) { + return false; + } + + final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizerForAllWindows(); + final RemoteAnimationDefinition definition = organizer != null + ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController + .getRemoteAnimationDefinition(organizer) + : null; + final RemoteAnimationAdapter adapter = definition != null + ? definition.getAdapter(transit, activityTypes) + : null; + if (adapter == null) { + return false; + } + mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(adapter); + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, + "Override with TaskFragment remote animation for transit=%s", + AppTransition.appTransitionOldToString(transit)); + return true; + } + /** * Overrides the pending transition with the remote animation defined for the transition in the * set of defined remote animations in the app window token. @@ -464,19 +668,28 @@ public class AppTransitionController { } final RemoteAnimationAdapter adapter = getRemoteAnimationOverride(animLpActivity, transit, activityTypes); - if (adapter != null) { + if (adapter != null + && mDisplayContent.mAppTransition.getRemoteAnimationController() == null) { mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(adapter); } } + @Nullable + static Task findRootTaskFromContainer(WindowContainer wc) { + return wc.asTaskFragment() != null ? wc.asTaskFragment().getRootTask() + : wc.asActivityRecord().getRootTask(); + } + + @Nullable static ActivityRecord getAppFromContainer(WindowContainer wc) { - return wc.asTask() != null ? wc.asTask().getTopNonFinishingActivity() + return wc.asTaskFragment() != null ? wc.asTaskFragment().getTopNonFinishingActivity() : wc.asActivityRecord(); } /** * @return The window token that determines the animation theme. */ + @Nullable private ActivityRecord findAnimLayoutParamsToken(@TransitionOldType int transit, ArraySet<Integer> activityTypes) { ActivityRecord result; @@ -489,7 +702,7 @@ public class AppTransitionController { w -> w.getRemoteAnimationDefinition() != null && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes)); if (result != null) { - return getAppFromContainer(result); + return result; } result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps, w -> w.fillsParent() && w.findMainWindow() != null); @@ -632,6 +845,7 @@ public class AppTransitionController { boolean canPromote = true; if (parent == null || !parent.canCreateRemoteAnimationTarget() + || !parent.canBeAnimationTarget() // We cannot promote the animation on Task's parent when the task is in // clearing task in case the animating get stuck when performing the opening // task that behind it. @@ -717,6 +931,10 @@ public class AppTransitionController { voiceInteraction); applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp, voiceInteraction); + final RecentsAnimationController rac = mService.getRecentsAnimationController(); + if (rac != null) { + rac.sendTasksAppeared(); + } for (int i = 0; i < openingApps.size(); ++i) { openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false; @@ -727,7 +945,7 @@ public class AppTransitionController { final AccessibilityController accessibilityController = mDisplayContent.mWmService.mAccessibilityController; - if (accessibilityController != null) { + if (accessibilityController.hasCallbacks()) { accessibilityController.onAppWindowTransition(mDisplayContent.getDisplayId(), transit); } } @@ -840,71 +1058,113 @@ public class AppTransitionController { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Checking %d opening apps (frozen=%b timeout=%b)...", apps.size(), mService.mDisplayFrozen, mDisplayContent.mAppTransition.isTimeout()); - + if (mDisplayContent.mAppTransition.isTimeout()) { + return true; + } final ScreenRotationAnimation screenRotationAnimation = mService.mRoot.getDisplayContent( Display.DEFAULT_DISPLAY).getRotationAnimation(); - if (!mDisplayContent.mAppTransition.isTimeout()) { - // Imagine the case where we are changing orientation due to an app transition, but a - // previous orientation change is still in progress. We won't process the orientation - // change for our transition because we need to wait for the rotation animation to - // finish. - // If we start the app transition at this point, we will interrupt it halfway with a - // new rotation animation after the old one finally finishes. It's better to defer the - // app transition. - if (screenRotationAnimation != null && screenRotationAnimation.isAnimating() - && mDisplayContent.getDisplayRotation().needsUpdate()) { - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, - "Delaying app transition for screen rotation animation to finish"); + // Imagine the case where we are changing orientation due to an app transition, but a + // previous orientation change is still in progress. We won't process the orientation + // change for our transition because we need to wait for the rotation animation to + // finish. + // If we start the app transition at this point, we will interrupt it halfway with a + // new rotation animation after the old one finally finishes. It's better to defer the + // app transition. + if (screenRotationAnimation != null && screenRotationAnimation.isAnimating() + && mDisplayContent.getDisplayRotation().needsUpdate()) { + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, + "Delaying app transition for screen rotation animation to finish"); + return false; + } + for (int i = 0; i < apps.size(); i++) { + WindowContainer wc = apps.valueAt(i); + final ActivityRecord activity = getAppFromContainer(wc); + if (activity == null) { + continue; + } + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, + "Check opening app=%s: allDrawn=%b startingDisplayed=%b " + + "startingMoved=%b isRelaunching()=%b startingWindow=%s", + activity, activity.allDrawn, activity.startingDisplayed, + activity.startingMoved, activity.isRelaunching(), + activity.mStartingWindow); + + final boolean allDrawn = activity.allDrawn && !activity.isRelaunching(); + if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) { return false; } - for (int i = 0; i < apps.size(); i++) { - WindowContainer wc = apps.valueAt(i); - final ActivityRecord activity = getAppFromContainer(wc); - if (activity == null) { - continue; - } - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, - "Check opening app=%s: allDrawn=%b startingDisplayed=%b " - + "startingMoved=%b isRelaunching()=%b startingWindow=%s", - activity, activity.allDrawn, activity.startingDisplayed, - activity.startingMoved, activity.isRelaunching(), - activity.mStartingWindow); + if (allDrawn) { + outReasons.put(activity, APP_TRANSITION_WINDOWS_DRAWN); + } else { + outReasons.put(activity, + activity.mStartingData instanceof SplashScreenStartingData + ? APP_TRANSITION_SPLASH_SCREEN + : APP_TRANSITION_SNAPSHOT); + } + } + // We also need to wait for the specs to be fetched, if needed. + if (mDisplayContent.mAppTransition.isFetchingAppTransitionsSpecs()) { + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "isFetchingAppTransitionSpecs=true"); + return false; + } - final boolean allDrawn = activity.allDrawn && !activity.isRelaunching(); - if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) { - return false; - } - if (allDrawn) { - outReasons.put(activity, APP_TRANSITION_WINDOWS_DRAWN); - } else { - outReasons.put(activity, - activity.mStartingData instanceof SplashScreenStartingData - ? APP_TRANSITION_SPLASH_SCREEN - : APP_TRANSITION_SNAPSHOT); - } - } + if (!mDisplayContent.mUnknownAppVisibilityController.allResolved()) { + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "unknownApps is not empty: %s", + mDisplayContent.mUnknownAppVisibilityController.getDebugMessage()); + return false; + } - // We also need to wait for the specs to be fetched, if needed. - if (mDisplayContent.mAppTransition.isFetchingAppTransitionsSpecs()) { - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "isFetchingAppTransitionSpecs=true"); - return false; - } + // If the wallpaper is visible, we need to check it's ready too. + return !mWallpaperControllerLocked.isWallpaperVisible() + || mWallpaperControllerLocked.wallpaperTransitionReady(); + } - if (!mDisplayContent.mUnknownAppVisibilityController.allResolved()) { - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "unknownApps is not empty: %s", - mDisplayContent.mUnknownAppVisibilityController.getDebugMessage()); - return false; - } + private boolean transitionGoodToGoForTaskFragments() { + if (mDisplayContent.mAppTransition.isTimeout()) { + return true; + } - // If the wallpaper is visible, we need to check it's ready too. - boolean wallpaperReady = !mWallpaperControllerLocked.isWallpaperVisible() || - mWallpaperControllerLocked.wallpaperTransitionReady(); - if (wallpaperReady) { - return true; + // Check all Tasks in this transition. This is needed because new TaskFragment created for + // launching activity may not be in the tracking lists, but we still want to wait for the + // activity launch to start the transition. + final ArraySet<Task> rootTasks = new ArraySet<>(); + for (int i = mDisplayContent.mOpeningApps.size() - 1; i >= 0; i--) { + rootTasks.add(mDisplayContent.mOpeningApps.valueAt(i).getRootTask()); + } + for (int i = mDisplayContent.mClosingApps.size() - 1; i >= 0; i--) { + rootTasks.add(mDisplayContent.mClosingApps.valueAt(i).getRootTask()); + } + for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) { + rootTasks.add( + findRootTaskFromContainer(mDisplayContent.mChangingContainers.valueAt(i))); + } + + // Organized TaskFragment can be empty for two situations: + // 1. New created and is waiting for Activity launch. In this case, we want to wait for + // the Activity launch to trigger the transition. + // 2. Last Activity is just removed. In this case, we want to wait for organizer to + // remove the TaskFragment because it may also want to change other TaskFragments in + // the same transition. + for (int i = rootTasks.size() - 1; i >= 0; i--) { + final Task rootTask = rootTasks.valueAt(i); + if (rootTask == null) { + // It is possible that one activity may have been removed from the hierarchy. No + // need to check for this case. + continue; + } + final boolean notReady = rootTask.forAllLeafTaskFragments(taskFragment -> { + if (!taskFragment.isReadyToTransit()) { + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Organized TaskFragment is not ready= %s", + taskFragment); + return true; + } + return false; + }); + if (notReady) { + return false; } - return false; } return true; } diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index faeb4ba36748..2a8ac39ead8d 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -20,9 +20,11 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; import android.annotation.NonNull; import android.util.ArraySet; +import android.util.Slog; import android.util.SparseArray; import android.view.SurfaceControl; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; /** @@ -65,6 +67,7 @@ class BLASTSyncEngine { class SyncGroup { final int mSyncId; final TransactionReadyListener mListener; + final Runnable mOnTimeout; boolean mReady = false; final ArraySet<WindowContainer> mRootMembers = new ArraySet<>(); private SurfaceControl.Transaction mOrphanTransaction = null; @@ -72,6 +75,12 @@ class BLASTSyncEngine { private SyncGroup(TransactionReadyListener listener, int id) { mSyncId = id; mListener = listener; + mOnTimeout = () -> { + Slog.w(TAG, "Sync group " + mSyncId + " timeout"); + synchronized (mWm.mGlobalLock) { + onTimeout(); + } + }; } /** @@ -114,6 +123,7 @@ class BLASTSyncEngine { } mListener.onTransactionReady(mSyncId, merged); mActiveSyncs.remove(mSyncId); + mWm.mH.removeCallbacks(mOnTimeout); } private void setReady(boolean ready) { @@ -136,6 +146,17 @@ class BLASTSyncEngine { void onCancelSync(WindowContainer wc) { mRootMembers.remove(wc); } + + private void onTimeout() { + if (!mActiveSyncs.contains(mSyncId)) return; + for (int i = mRootMembers.size() - 1; i >= 0; --i) { + final WindowContainer<?> wc = mRootMembers.valueAt(i); + if (!wc.isSyncFinished()) { + Slog.i(TAG, "Unfinished container: " + wc); + } + } + finishNow(); + } } private final WindowManagerService mWm; @@ -147,13 +168,23 @@ class BLASTSyncEngine { } int startSyncSet(TransactionReadyListener listener) { + return startSyncSet(listener, WindowState.BLAST_TIMEOUT_DURATION); + } + + int startSyncSet(TransactionReadyListener listener, long timeoutMs) { final int id = mNextSyncId++; final SyncGroup s = new SyncGroup(listener, id); mActiveSyncs.put(id, s); ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s", id, listener); + scheduleTimeout(s, timeoutMs); return id; } + @VisibleForTesting + void scheduleTimeout(SyncGroup s, long timeoutMs) { + mWm.mH.postDelayed(s.mOnTimeout, timeoutMs); + } + void addToSyncSet(int id, WindowContainer wc) { mActiveSyncs.get(id).addToSync(wc); } @@ -166,6 +197,10 @@ class BLASTSyncEngine { setReady(id, true); } + boolean isReady(int id) { + return mActiveSyncs.get(id).mReady; + } + /** * Aborts the sync (ie. it doesn't wait for ready or anything to finish) */ diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java index 71a10df34d30..0afd87282783 100644 --- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java +++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java @@ -20,6 +20,8 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVIT import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS; +import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW; +import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY; import android.annotation.NonNull; import android.annotation.Nullable; @@ -70,13 +72,13 @@ class BackgroundLaunchProcessController { } boolean areBackgroundActivityStartsAllowed(int pid, int uid, String packageName, - boolean appSwitchAllowed, boolean isCheckingForFgsStart, + int appSwitchState, boolean isCheckingForFgsStart, boolean hasActivityInVisibleTask, boolean hasBackgroundActivityStartPrivileges, long lastStopAppSwitchesTime, long lastActivityLaunchTime, long lastActivityFinishTime) { // If app switching is not allowed, we ignore all the start activity grace period // exception so apps cannot start itself in onPause() after pressing home button. - if (appSwitchAllowed) { + if (appSwitchState == APP_SWITCH_ALLOW) { // Allow if any activity in the caller has either started or finished very recently, and // it must be started or finished after last stop app switches time. final long now = SystemClock.uptimeMillis(); @@ -111,7 +113,8 @@ class BackgroundLaunchProcessController { return true; } // Allow if the caller has an activity in any foreground task. - if (appSwitchAllowed && hasActivityInVisibleTask) { + if (hasActivityInVisibleTask + && (appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY)) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "[Process(" + pid + ")] Activity start allowed: process has activity in foreground task"); diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index d52e9b608cdb..5a2cf17ffd18 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -28,16 +28,20 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; +import static android.app.WindowConfigurationProto.WINDOWING_MODE; +import static android.content.ConfigurationProto.WINDOW_CONFIGURATION; import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION; import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION; import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGURATION; import android.annotation.CallSuper; +import android.annotation.NonNull; import android.app.WindowConfiguration; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; +import android.os.LocaleList; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; @@ -111,6 +115,7 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { * This method should be used for getting settings applied in each particular level of the * hierarchy. */ + @NonNull public Configuration getConfiguration() { return mFullConfiguration; } @@ -170,11 +175,13 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } /** Returns requested override configuration applied to this configuration container. */ + @NonNull public Configuration getRequestedOverrideConfiguration() { return mRequestedOverrideConfiguration; } /** Returns the resolved override configuration. */ + @NonNull Configuration getResolvedOverrideConfiguration() { return mResolvedOverrideConfiguration; } @@ -203,6 +210,7 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { * Get merged override configuration from the top of the hierarchy down to this particular * instance. This should be reported to client as override config. */ + @NonNull public Configuration getMergedOverrideConfiguration() { return mMergedOverrideConfiguration; } @@ -507,7 +515,7 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM; } - /** Returns the activity type associated with the the configuration container. */ + /** Returns the activity type associated with the configuration container. */ /*@WindowConfiguration.ActivityType*/ public int getActivityType() { return mFullConfiguration.windowConfiguration.getActivityType(); @@ -541,20 +549,48 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } /** + * Applies app-specific nightMode and {@link LocaleList} on requested configuration. + * @return true if any of the requested configuration has been updated. + */ + public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales) { + mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration()); + boolean newNightModeSet = (nightMode != null) && setOverrideNightMode(mRequestsTmpConfig, + nightMode); + boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig, + locales); + if (newNightModeSet || newLocalesSet) { + onRequestedOverrideConfigurationChanged(mRequestsTmpConfig); + } + return newNightModeSet || newLocalesSet; + } + + /** * Overrides the night mode applied to this ConfigurationContainer. * @return true if the nightMode has been changed. */ - public boolean setOverrideNightMode(int nightMode) { + private boolean setOverrideNightMode(Configuration requestsTmpConfig, int nightMode) { final int currentUiMode = mRequestedOverrideConfiguration.uiMode; final int currentNightMode = currentUiMode & Configuration.UI_MODE_NIGHT_MASK; final int validNightMode = nightMode & Configuration.UI_MODE_NIGHT_MASK; if (currentNightMode == validNightMode) { return false; } - mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration()); - mRequestsTmpConfig.uiMode = validNightMode + requestsTmpConfig.uiMode = validNightMode | (currentUiMode & ~Configuration.UI_MODE_NIGHT_MASK); - onRequestedOverrideConfigurationChanged(mRequestsTmpConfig); + return true; + } + + /** + * Overrides the locales applied to this ConfigurationContainer. + * @return true if the LocaleList has been changed. + */ + private boolean setOverrideLocales(Configuration requestsTmpConfig, + @NonNull LocaleList overrideLocales) { + if (mRequestedOverrideConfiguration.getLocales().equals(overrideLocales)) { + return false; + } + requestsTmpConfig.setLocales(overrideLocales); + requestsTmpConfig.userSetLocale = true; return true; } @@ -661,22 +697,40 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { @CallSuper protected void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { - // Critical log level logs only visible elements to mitigate performance overheard - if (logLevel != WindowTraceLogLevel.ALL && !mHasOverrideConfiguration) { - return; + final long token = proto.start(fieldId); + + if (logLevel == WindowTraceLogLevel.ALL || mHasOverrideConfiguration) { + mRequestedOverrideConfiguration.dumpDebug(proto, OVERRIDE_CONFIGURATION, + logLevel == WindowTraceLogLevel.CRITICAL); } - final long token = proto.start(fieldId); - mRequestedOverrideConfiguration.dumpDebug(proto, OVERRIDE_CONFIGURATION, - logLevel == WindowTraceLogLevel.CRITICAL); + // Unless trace level is set to `WindowTraceLogLevel.ALL` don't dump anything that isn't + // required to mitigate performance overhead if (logLevel == WindowTraceLogLevel.ALL) { mFullConfiguration.dumpDebug(proto, FULL_CONFIGURATION, false /* critical */); mMergedOverrideConfiguration.dumpDebug(proto, MERGED_OVERRIDE_CONFIGURATION, false /* critical */); } + + if (logLevel == WindowTraceLogLevel.TRIM) { + // Required for Fass to automatically detect pip transitions in Winscope traces + dumpDebugWindowingMode(proto); + } + proto.end(token); } + private void dumpDebugWindowingMode(ProtoOutputStream proto) { + final long fullConfigToken = proto.start(FULL_CONFIGURATION); + final long windowConfigToken = proto.start(WINDOW_CONFIGURATION); + + int windowingMode = mFullConfiguration.windowConfiguration.getWindowingMode(); + proto.write(WINDOWING_MODE, windowingMode); + + proto.end(windowConfigToken); + proto.end(fullConfigToken); + } + /** * Dumps the names of this container children in the input print writer indenting each * level with the input prefix. diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index baa27e34d625..99f6fd4771b7 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -495,8 +495,10 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { DisplayAreaInfo getDisplayAreaInfo() { - DisplayAreaInfo info = new DisplayAreaInfo(mRemoteToken.toWindowContainerToken(), + final DisplayAreaInfo info = new DisplayAreaInfo(mRemoteToken.toWindowContainerToken(), getDisplayContent().getDisplayId(), mFeatureId); + final RootDisplayArea root = getRootDisplayArea(); + info.rootDisplayAreaId = root == null ? getDisplayContent().mFeatureId : root.mFeatureId; info.configuration.setTo(getConfiguration()); return info; } diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java index 47d7c9d1279d..3d7ac6c1a3f8 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java @@ -25,6 +25,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER; import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST; +import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID; import android.annotation.Nullable; import android.os.Bundle; @@ -135,12 +136,6 @@ import java.util.function.BiFunction; */ class DisplayAreaPolicyBuilder { - /** - * Key to specify the {@link RootDisplayArea} to attach the window to. Should be used by the - * function passed in from {@link #setSelectRootForWindowFunc(BiFunction)} - */ - static final String KEY_ROOT_DISPLAY_AREA_ID = "root_display_area_id"; - @Nullable private HierarchyBuilder mRootHierarchyBuilder; private final ArrayList<HierarchyBuilder> mDisplayAreaGroupHierarchyBuilders = new ArrayList<>(); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 8a9d5d438a47..42c6dd43ebce 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -44,6 +44,8 @@ import static android.view.Display.FLAG_PRIVATE; import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; import static android.view.Display.INVALID_DISPLAY; import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT; +import static android.view.Display.STATE_UNKNOWN; +import static android.view.Display.isSuspendedState; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_LEFT_GESTURES; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; @@ -62,7 +64,6 @@ import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; -import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; @@ -79,6 +80,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.DisplayAreaOrganizer.FEATURE_ROOT; @@ -90,13 +92,17 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LAYER_MIRRORING; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; +import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.DisplayContentProto.APP_TRANSITION; import static com.android.server.wm.DisplayContentProto.CLOSING_APPS; import static com.android.server.wm.DisplayContentProto.CURRENT_FOCUS; @@ -113,13 +119,15 @@ import static com.android.server.wm.DisplayContentProto.IME_POLICY; import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_CONTROL_TARGET; import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_INPUT_TARGET; import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_TARGET; +import static com.android.server.wm.DisplayContentProto.IS_SLEEPING; import static com.android.server.wm.DisplayContentProto.OPENING_APPS; import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY; import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA; import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION; +import static com.android.server.wm.DisplayContentProto.SLEEP_TOKENS; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; -import static com.android.server.wm.Task.ActivityState.RESUMED; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainerChildProto.DISPLAY_CONTENT; @@ -128,7 +136,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -136,7 +143,6 @@ import static com.android.server.wm.WindowManagerService.H.REPORT_HARD_KEYBOARD_ import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT; import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES; -import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT; @@ -204,6 +210,7 @@ import android.view.InputWindowHandle; import android.view.InsetsSource; import android.view.InsetsState; import android.view.InsetsState.InternalInsetsType; +import android.view.InsetsVisibilities; import android.view.MagnificationSpec; import android.view.PrivacyIndicatorBounds; import android.view.RemoteAnimationDefinition; @@ -298,7 +305,24 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * The direct child layer of the display to put all non-overlay windows. This is also used for * screen rotation animation so that there is a parent layer to put the animation leash. */ - private final SurfaceControl mWindowingLayer; + private SurfaceControl mWindowingLayer; + + /** + * The window token of the layer of the hierarchy to mirror, or null if this DisplayContent + * is not being used for layer mirroring. + */ + @VisibleForTesting IBinder mTokenToMirror = null; + + /** + * The surface for mirroring the contents of this hierarchy, or null if layer mirroring is + * temporarily disabled. + */ + private SurfaceControl mMirroredSurface = null; + + /** + * The last bounds of the DisplayArea to mirror. + */ + private Rect mLastMirroredDisplayAreaBounds = null; // Contains all IME window containers. Note that the z-ordering of the IME windows will depend // on the IME target. We mainly have this container grouping so we can keep track of all the IME @@ -308,7 +332,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp private final ImeContainer mImeWindowsContainer = new ImeContainer(mWmService); @VisibleForTesting - final DisplayAreaPolicy mDisplayAreaPolicy; + DisplayAreaPolicy mDisplayAreaPolicy; private WindowState mTmpWindow; private boolean mUpdateImeTarget; @@ -364,6 +388,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp boolean mIsSizeForced = false; /** + * Overridden display size and metrics to activity window bounds. Set via + * "adb shell wm set-sandbox-display-apis". Default to true, since only disable for debugging. + * @see WindowManagerService#setSandboxDisplayApis(int, boolean) + */ + private boolean mSandboxDisplayApis = true; + + /** * Overridden display density for current user. Initialized with {@link #mInitialDisplayDensity} * but can be set from Settings or via shell command "adb shell wm density". * @see WindowManagerService#setForcedDisplayDensityForUser(int, int, int) @@ -428,6 +459,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Accessed directly by all users. private boolean mLayoutNeeded; int pendingLayoutChanges; + boolean mLayoutAndAssignWindowLayersScheduled; /** * Used to gate application window layout until we have sent the complete configuration. @@ -500,11 +532,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp WindowState mCurrentFocus = null; /** - * The last focused window that we've notified the client that the focus is changed. - */ - WindowState mLastFocus = null; - - /** * The foreground app of this display. Windows below this app cannot be the focused window. If * the user taps on the area outside of the task of the focused app, we will notify AM about the * new task the user wants to interact with. @@ -555,6 +582,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * Specifies the count to determine whether to defer updating the IME target until ready. */ private int mDeferUpdateImeTargetCount; + private boolean mUpdateImeRequestedWhileDeferred; private MagnificationSpec mMagnificationSpec; @@ -692,6 +720,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // well and thus won't change the top resumed / focused record boolean mDontMoveToTop; + private final ArrayList<ActivityRecord> mTmpActivityList = new ArrayList<>(); + private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> { WindowStateAnimator winAnimator = w.mWinAnimator; final ActivityRecord activity = w.mActivityRecord; @@ -773,6 +803,21 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mTmpWindow = null; return true; } + + // If the candidate activity is currently being embedded in the focused task, the + // activity cannot be focused unless it is on the same TaskFragment as the focusedApp's. + TaskFragment parent = activity.getTaskFragment(); + if (parent != null && parent.isEmbedded()) { + Task hostTask = focusedApp.getTask(); + if (hostTask.isEmbedded()) { + // Use the hosting task if the current task is embedded. + hostTask = hostTask.getParent().asTaskFragment().getTask(); + } + if (activity.isDescendantOf(hostTask) + && activity.getTaskFragment() != focusedApp.getTaskFragment()) { + return false; + } + } } ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: Found new focus @ %s", w); @@ -961,8 +1006,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final boolean committed = winAnimator.commitFinishDrawingLocked(); if (isDefaultDisplay && committed) { if (w.hasWallpaper()) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "First draw done in potential wallpaper target " + w); + ProtoLog.v(WM_DEBUG_WALLPAPER, + "First draw done in potential wallpaper target %s", w); mWallpaperMayChange = true; pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; if (DEBUG_LAYOUT_REPEATS) { @@ -1060,52 +1105,91 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mDividerControllerLocked = new DockedTaskDividerController(this); mPinnedTaskController = new PinnedTaskController(mWmService, this); + final Transaction pendingTransaction = getPendingTransaction(); + configureSurfaces(pendingTransaction); + pendingTransaction.apply(); + + // Sets the display content for the children. + onDisplayChanged(this); + updateDisplayAreaOrganizers(); + + mInputMonitor = new InputMonitor(mWmService, this); + mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this); + + if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display); + + mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this); + } + + @Override + void migrateToNewSurfaceControl(Transaction t) { + t.remove(mSurfaceControl); + + mLastSurfacePosition.set(0, 0); + + configureSurfaces(t); + + for (int i = 0; i < mChildren.size(); i++) { + SurfaceControl sc = mChildren.get(i).getSurfaceControl(); + if (sc != null) { + t.reparent(sc, mSurfaceControl); + } + } + + scheduleAnimation(); + } + + /** + * Configures the surfaces hierarchy for DisplayContent + * This method always recreates the main surface control but reparents the children + * if they are already created. + * @param transaction as part of which to perform the configuration + */ + private void configureSurfaces(Transaction transaction) { final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession) .setOpaque(true) .setContainerLayer() .setCallsite("DisplayContent"); - mSurfaceControl = b.setName("Root").setContainerLayer().build(); + mSurfaceControl = b.setName(getName()).setContainerLayer().build(); - // Setup the policy and build the display area hierarchy. - mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate( - mWmService, this /* content */, this /* root */, mImeWindowsContainer); + if (mDisplayAreaPolicy == null) { + // Setup the policy and build the display area hierarchy. + // Build the hierarchy only after creating the surface so it is reparented correctly + mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate( + mWmService, this /* content */, this /* root */, + mImeWindowsContainer); + } final List<DisplayArea<? extends WindowContainer>> areas = mDisplayAreaPolicy.getDisplayAreas(FEATURE_WINDOWED_MAGNIFICATION); final DisplayArea<?> area = areas.size() == 1 ? areas.get(0) : null; + if (area != null && area.getParent() == this) { // The windowed magnification area should contain all non-overlay windows, so just use // it as the windowing layer. mWindowingLayer = area.mSurfaceControl; + transaction.reparent(mWindowingLayer, mSurfaceControl); } else { // Need an additional layer for screen level animation, so move the layer containing // the windows to the new root. mWindowingLayer = mSurfaceControl; mSurfaceControl = b.setName("RootWrapper").build(); - getPendingTransaction().reparent(mWindowingLayer, mSurfaceControl) + transaction.reparent(mWindowingLayer, mSurfaceControl) .show(mWindowingLayer); } - mOverlayLayer = b.setName("Display Overlays").setParent(mSurfaceControl).build(); + if (mOverlayLayer == null) { + mOverlayLayer = b.setName("Display Overlays").setParent(mSurfaceControl).build(); + } else { + transaction.reparent(mOverlayLayer, mSurfaceControl); + } - getPendingTransaction() + transaction .setLayer(mSurfaceControl, 0) .setLayerStack(mSurfaceControl, mDisplayId) .show(mSurfaceControl) .setLayer(mOverlayLayer, Integer.MAX_VALUE) .show(mOverlayLayer); - getPendingTransaction().apply(); - - // Sets the display content for the children. - onDisplayChanged(this); - updateDisplayAreaOrganizers(); - - mInputMonitor = new InputMonitor(mWmService, this); - mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this); - - if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display); - - mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this); } boolean isReady() { @@ -1236,17 +1320,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // removing from parent. token.getParent().removeChild(token); } - if (token.hasChild(prevDc.mLastFocus)) { - // If the reparent window token contains previous display's last focus window, means - // it will end up to gain window focus on the target display, so it should not be - // notified that it lost focus from the previous display. - prevDc.mLastFocus = null; - } } addWindowToken(token.token, token); - if (mWmService.mAccessibilityController != null) { + if (mWmService.mAccessibilityController.hasCallbacks()) { final int prevDisplayId = prevDc != null ? prevDc.getDisplayId() : INVALID_DISPLAY; mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(prevDisplayId, getDisplayId()); @@ -1352,11 +1430,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final Configuration currentDisplayConfig = getConfiguration(); mTmpConfiguration.setTo(currentDisplayConfig); computeScreenConfiguration(mTmpConfiguration); - configChanged |= currentDisplayConfig.diff(mTmpConfiguration) != 0; + final int changes = currentDisplayConfig.diff(mTmpConfiguration); + configChanged |= changes != 0; if (configChanged) { mWaitingForConfig = true; - mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this); + if (mTransitionController.isShellTransitionsEnabled()) { + requestChangeTransitionIfNeeded(changes); + } else { + mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this); + } sendNewConfiguration(); } @@ -1472,7 +1555,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } else if (currentConfig != null // If waiting for a remote rotation, don't prematurely update configuration. && !(mDisplayRotation.isWaitingForRemoteRotation() - || mAtmService.getTransitionController().isCollecting(this))) { + || mTransitionController.isCollecting(this))) { // No obvious action we need to take, but if our current state mismatches the // activity manager's, update it, disregarding font scale, which should remain set // to the value of the previous configuration. @@ -1570,11 +1653,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // to cover the activity configuration change. return false; } - if (r.attachedToProcess() && mayImeShowOnLaunchingActivity(r)) { - // Currently it is unknown that when will IME window be ready. Reject the case to - // avoid flickering by showing IME in inconsistent orientation. - return false; - } if (checkOpening) { if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) { // Apply normal rotation animation in case of the activity set different requested @@ -1623,7 +1701,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } /** Returns {@code true} if the IME is possible to show on the launching activity. */ - private boolean mayImeShowOnLaunchingActivity(@NonNull ActivityRecord r) { + boolean mayImeShowOnLaunchingActivity(@NonNull ActivityRecord r) { final WindowState win = r.findMainWindow(); if (win == null) { return false; @@ -1871,8 +1949,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private void applyRotation(final int oldRotation, final int rotation) { mDisplayRotation.applyCurrentRotation(rotation); - final boolean shellTransitions = - mWmService.mAtmService.getTransitionController().getTransitionPlayer() != null; + final boolean shellTransitions = mTransitionController.getTransitionPlayer() != null; final boolean rotateSeamlessly = mDisplayRotation.isRotatingSeamlessly() && !shellTransitions; final Transaction transaction = @@ -1921,10 +1998,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } } - - if (mWmService.mAccessibilityController != null) { - mWmService.mAccessibilityController.onRotationChanged(this); - } } void configureDisplayPolicy() { @@ -1964,16 +2037,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout(); final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation); - final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, - displayCutout); - final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, + final Point appSize = mDisplayPolicy.getNonDecorDisplaySize(dw, dh, rotation, uiMode, displayCutout); mDisplayInfo.rotation = rotation; mDisplayInfo.logicalWidth = dw; mDisplayInfo.logicalHeight = dh; mDisplayInfo.logicalDensityDpi = mBaseDisplayDensity; - mDisplayInfo.appWidth = appWidth; - mDisplayInfo.appHeight = appHeight; + mDisplayInfo.appWidth = appSize.x; + mDisplayInfo.appHeight = appSize.y; if (isDefaultDisplay) { mDisplayInfo.getLogicalMetrics(mRealDisplayMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); @@ -2102,24 +2173,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Compute configuration related to application without changing current display. */ private void computeScreenAppConfiguration(Configuration outConfig, int dw, int dh, int rotation, int uiMode, DisplayCutout displayCutout) { - final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, - displayCutout); - final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, + final Point appSize = mDisplayPolicy.getNonDecorDisplaySize(dw, dh, rotation, uiMode, displayCutout); mDisplayPolicy.getNonDecorInsetsLw(rotation, dw, dh, displayCutout, mTmpRect); final int leftInset = mTmpRect.left; final int topInset = mTmpRect.top; // AppBounds at the root level should mirror the app screen size. outConfig.windowConfiguration.setAppBounds(leftInset /* left */, topInset /* top */, - leftInset + appWidth /* right */, topInset + appHeight /* bottom */); + leftInset + appSize.x /* right */, topInset + appSize.y /* bottom */); outConfig.windowConfiguration.setRotation(rotation); outConfig.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; final float density = mDisplayMetrics.density; - outConfig.screenWidthDp = (int) (mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation, - uiMode, displayCutout) / density); - outConfig.screenHeightDp = (int) (mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation, - uiMode, displayCutout) / density); + final Point configSize = mDisplayPolicy.getConfigDisplaySize(dw, dh, rotation, uiMode, + displayCutout); + outConfig.screenWidthDp = (int) (configSize.x / density); + outConfig.screenHeightDp = (int) (configSize.y / density); outConfig.compatScreenWidthDp = (int) (outConfig.screenWidthDp / mCompatibleScreenScale); outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale); @@ -2258,10 +2327,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp DisplayMetrics dm, int dw, int dh) { final DisplayCutout displayCutout = calculateDisplayCutoutForRotation( rotation).getDisplayCutout(); - dm.noncompatWidthPixels = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, - displayCutout); - dm.noncompatHeightPixels = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, + final Point nonDecorSize = mDisplayPolicy.getNonDecorDisplaySize(dw, dh, rotation, uiMode, displayCutout); + dm.noncompatWidthPixels = nonDecorSize.x; + dm.noncompatHeightPixels = nonDecorSize.y; float scale = CompatibilityInfo.computeCompatibleScaling(dm, null); int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f); if (curSize == 0 || size < curSize) { @@ -2313,12 +2382,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp rotation).getDisplayCutout(); // Get the app screen size at this rotation. - int w = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayCutout); - int h = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, displayCutout); + final Point size = mDisplayPolicy.getNonDecorDisplaySize(dw, dh, rotation, uiMode, + displayCutout); // Compute the screen layout size class for this rotation. - int longSize = w; - int shortSize = h; + int longSize = size.x; + int shortSize = size.y; if (longSize < shortSize) { int tmp = longSize; longSize = shortSize; @@ -2333,21 +2402,19 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp int uiMode, int dw, int dh) { final DisplayCutout displayCutout = calculateDisplayCutoutForRotation( rotation).getDisplayCutout(); - final int width = mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode, + final Point size = mDisplayPolicy.getConfigDisplaySize(dw, dh, rotation, uiMode, displayCutout); - if (width < displayInfo.smallestNominalAppWidth) { - displayInfo.smallestNominalAppWidth = width; + if (size.x < displayInfo.smallestNominalAppWidth) { + displayInfo.smallestNominalAppWidth = size.x; } - if (width > displayInfo.largestNominalAppWidth) { - displayInfo.largestNominalAppWidth = width; + if (size.x > displayInfo.largestNominalAppWidth) { + displayInfo.largestNominalAppWidth = size.x; } - final int height = mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode, - displayCutout); - if (height < displayInfo.smallestNominalAppHeight) { - displayInfo.smallestNominalAppHeight = height; + if (size.y < displayInfo.smallestNominalAppHeight) { + displayInfo.smallestNominalAppHeight = size.y; } - if (height > displayInfo.largestNominalAppHeight) { - displayInfo.largestNominalAppHeight = height; + if (size.y > displayInfo.largestNominalAppHeight) { + displayInfo.largestNominalAppHeight = size.y; } } @@ -2472,6 +2539,48 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Update IME parent if needed. updateImeParent(); + // Update mirroring surface for MediaProjection, if this DisplayContent is being used + // for layer mirroring. + if (isCurrentlyMirroring() && mLastMirroredDisplayAreaBounds != null) { + // Mirroring has already begun, but update mirroring since the display is now on. + final WindowContainer wc = mWmService.mWindowContextListenerController.getContainer( + mTokenToMirror); + if (wc == null) { + ProtoLog.v(WM_DEBUG_LAYER_MIRRORING, + "Unable to retrieve window container to update layer mirroring for " + + "display %d", + mDisplayId); + return; + } + + ProtoLog.v(WM_DEBUG_LAYER_MIRRORING, + "Display %d was already layer mirroring, so apply transformations if necessary", + mDisplayId); + // Retrieve the size of the DisplayArea to mirror, and continue with the update + // if the bounds or orientation has changed. + final Rect displayAreaBounds = wc.getDisplayContent().getBounds(); + int displayAreaOrientation = wc.getDisplayContent().getOrientation(); + if (!mLastMirroredDisplayAreaBounds.equals(displayAreaBounds) + || lastOrientation != displayAreaOrientation) { + Point surfaceSize = fetchSurfaceSizeIfPresent(); + if (surfaceSize != null) { + ProtoLog.v(WM_DEBUG_LAYER_MIRRORING, + "Going ahead with updating layer mirroring for display %d to new " + + "bounds %s and/or orientation %d.", + mDisplayId, displayAreaBounds, displayAreaOrientation); + updateMirroredSurface(mWmService.mTransactionFactory.get(), + displayAreaBounds, surfaceSize); + } else { + // If the surface removed, do nothing. We will handle this via onDisplayChanged + // (the display will be off if the surface is removed). + ProtoLog.v(WM_DEBUG_LAYER_MIRRORING, + "Unable to update layer mirroring for display %d to new bounds %s" + + " and/or orientation %d, since the surface is not available.", + mDisplayId, displayAreaBounds, displayAreaOrientation); + } + } + } + if (lastOrientation != getConfiguration().orientation) { getMetricsLogger().write( new LogMaker(MetricsEvent.ACTION_PHONE_ORIENTATION_CHANGED) @@ -2492,7 +2601,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Override boolean isVisibleRequested() { - return isVisible(); + return isVisible() && !mRemoved && !mRemoving; } @Override @@ -2986,7 +3095,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Override void removeIfPossible() { - if (isAnimating(TRANSITION | PARENTS)) { + if (isAnimating(TRANSITION | PARENTS) + // isAnimating is a legacy transition query and will be removed, so also add a + // check for whether this is in a shell-transition when not using legacy. + || mTransitionController.inTransition()) { mDeferredRemoval = true; return; } @@ -3016,6 +3128,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mOverlayLayer.release(); mInputMonitor.onDisplayRemoved(); mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this); + mWmService.mAccessibilityController.onDisplayRemoved(mDisplayId); } finally { mDisplayReady = false; } @@ -3087,6 +3200,35 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mScreenRotationAnimation; } + /** + * Requests to start a transition for the display configuration change. The given changes must + * be non-zero. This method is no-op if the display has been collected. + */ + void requestChangeTransitionIfNeeded(@ActivityInfo.Config int changes) { + final TransitionController controller = mTransitionController; + if (controller.isCollecting()) { + if (!controller.isCollecting(this)) { + controller.collect(this); + } + return; + } + final Transition t = controller.requestTransitionIfNeeded(TRANSIT_CHANGE, this); + if (t != null) { + if (getRotation() != getWindowConfiguration().getRotation()) { + mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN); + controller.mTransitionMetricsReporter.associate(t, + startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN)); + } + t.setKnownConfigChanges(this, changes); + } + } + + /** If the display is in transition, there should be a screenshot covering it. */ + @Override + boolean inTransition() { + return mScreenRotationAnimation != null || super.inTransition(); + } + @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { @@ -3107,7 +3249,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp screenRotationAnimation.dumpDebug(proto, SCREEN_ROTATION_ANIMATION); } mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES); - mAppTransition.dumpDebug(proto, APP_TRANSITION); + if (mTransitionController.isShellTransitionsEnabled()) { + mTransitionController.dumpDebugLegacy(proto, APP_TRANSITION); + } else { + mAppTransition.dumpDebug(proto, APP_TRANSITION); + } if (mFocusedApp != null) { mFocusedApp.writeNameToProto(proto, FOCUSED_APP); } @@ -3130,6 +3276,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp proto.write(FOCUSED_ROOT_TASK_ID, INVALID_TASK_ID); } proto.write(DISPLAY_READY, isReady()); + proto.write(IS_SLEEPING, isSleeping()); + for (int i = 0; i < mAllSleepTokens.size(); ++i) { + mAllSleepTokens.get(i).writeTagToProto(proto, SLEEP_TOKENS); + } + if (mImeLayeringTarget != null) { mImeLayeringTarget.dumpDebug(proto, INPUT_METHOD_TARGET, logLevel); } @@ -3195,9 +3346,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp pw.print(prefix); pw.print("mLayoutSeq="); pw.println(mLayoutSeq); pw.print(" mCurrentFocus="); pw.println(mCurrentFocus); - if (mLastFocus != mCurrentFocus) { - pw.print(" mLastFocus="); pw.println(mLastFocus); - } pw.print(" mFocusedApp="); pw.println(mFocusedApp); if (mFixedRotationLaunchingApp != null) { pw.println(" mFixedRotationLaunchingApp=" + mFixedRotationLaunchingApp); @@ -3289,7 +3437,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Override public String toString() { - return "Display " + mDisplayId + " info=" + mDisplayInfo + " rootTasks=" + mChildren; + return "Display{#" + mDisplayId + " state=" + Display.stateToString(mDisplayInfo.state) + + " size=" + mDisplayInfo.logicalWidth + "x" + mDisplayInfo.logicalHeight + + " " + Surface.rotationToString(mDisplayInfo.rotation) + "}"; } String getName() { @@ -3428,15 +3578,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } - onWindowFocusChanged(oldFocus, newFocus); - - int focusChanged = getDisplayPolicy().focusChangedLw(oldFocus, newFocus); + getDisplayPolicy().focusChangedLw(oldFocus, newFocus); if (imWindowChanged && oldFocus != mInputMethodWindow) { // Focus of the input method window changed. Perform layout if needed. if (mode == UPDATE_FOCUS_PLACING_SURFACES) { performLayout(true /*initial*/, updateInputWindows); - focusChanged &= ~FINISH_LAYOUT_REDO_LAYOUT; } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) { // Client will do the layout, but we need to assign layers // for handleNewWindowLocked() below. @@ -3444,16 +3591,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } - if ((focusChanged & FINISH_LAYOUT_REDO_LAYOUT) != 0) { - // The change in focus caused us to need to do a layout. Okay. - setLayoutNeeded(); - if (mode == UPDATE_FOCUS_PLACING_SURFACES) { - performLayout(true /*initial*/, updateInputWindows); - } else if (mode == UPDATE_FOCUS_REMOVING_FOCUS) { - mWmService.mRoot.performSurfacePlacement(); - } - } - if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) { // If we defer assigning layers, then the caller is responsible for doing this part. getInputMonitor().setInputFocusLw(newFocus, updateInputWindows); @@ -3474,13 +3611,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // focused one starts firing events. // TODO(b/151179149) investigate what info accessibility service needs before input can // dispatch focus to clients. - if (mWmService.mAccessibilityController != null) { + if (mWmService.mAccessibilityController.hasCallbacks()) { mWmService.mH.sendMessage(PooledLambda.obtainMessage( this::updateAccessibilityOnWindowFocusChanged, mWmService.mAccessibilityController)); } - mLastFocus = mCurrentFocus; return true; } @@ -3488,20 +3624,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp accessibilityController.onWindowFocusChangedNot(getDisplayId()); } - private static void onWindowFocusChanged(WindowState oldFocus, WindowState newFocus) { - final Task focusedTask = newFocus != null ? newFocus.getTask() : null; - final Task unfocusedTask = oldFocus != null ? oldFocus.getTask() : null; - if (focusedTask == unfocusedTask) { - return; - } - if (focusedTask != null) { - focusedTask.onWindowFocusChanged(true /* hasFocus */); - } - if (unfocusedTask != null) { - unfocusedTask.onWindowFocusChanged(false /* hasFocus */); - } - } - /** * Set the new focused app to this display. * @@ -3525,7 +3647,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "setFocusedApp %s displayId=%d Callers=%s", newFocus, getDisplayId(), Debug.getCallers(4)); + final Task oldTask = mFocusedApp != null ? mFocusedApp.getTask() : null; + final Task newTask = newFocus != null ? newFocus.getTask() : null; mFocusedApp = newFocus; + if (oldTask != newTask) { + if (oldTask != null) oldTask.onAppFocusChanged(false); + if (newTask != null) newTask.onAppFocusChanged(true); + } + getInputMonitor().setFocusedAppLw(newFocus); updateTouchExcludeRegion(); return true; @@ -3666,6 +3795,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final WindowState curTarget = mImeLayeringTarget; if (!canUpdateImeTarget()) { if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Defer updating IME target"); + mUpdateImeRequestedWhileDeferred = true; return curTarget; } @@ -3716,7 +3846,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } boolean shouldImeAttachedToApp() { - return isImeControlledByApp() + // Force attaching IME to the display when magnifying, or it would be magnified with + // target app together. + final boolean allowAttachToApp = (mMagnificationSpec == null); + + return allowAttachToApp && isImeControlledByApp() && mImeLayeringTarget != null && mImeLayeringTarget.mActivityRecord != null && mImeLayeringTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN @@ -3798,6 +3932,23 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mWmService.mForceDesktopModeOnExternalDisplays && !isDefaultDisplay && !isPrivate(); } + /** @see WindowManagerInternal#onToggleImeRequested */ + void onShowImeRequested() { + if (mImeLayeringTarget == null || mInputMethodWindow == null) { + return; + } + // If IME window will be shown on the rotated activity, share the transformed state to + // IME window so it can compute rotated frame with rotated configuration. + if (mImeLayeringTarget.mToken.isFixedRotationTransforming()) { + mInputMethodWindow.mToken.linkFixedRotationTransform(mImeLayeringTarget.mToken); + // Hide the window until the rotation is done to avoid intermediate artifacts if the + // parent surface of IME container is changed. + if (mFadeRotationAnimationController != null) { + mFadeRotationAnimationController.hideImmediately(mInputMethodWindow.mToken); + } + } + } + @VisibleForTesting void setImeLayeringTarget(WindowState target) { mImeLayeringTarget = target; @@ -3973,12 +4124,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void updateImeInputAndControlTarget(WindowState target) { if (mImeInputTarget != target) { ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target); - if (target != null && target.mActivityRecord != null) { - target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false; - } setImeInputTarget(target); + mInsetsStateController.updateAboveInsetsState(mInputMethodWindow, mInsetsStateController + .getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME)); updateImeControlTarget(); } + // Unfreeze IME insets after the new target updated, in case updateAboveInsetsState may + // deliver unrelated IME insets change to the non-IME requester. + if (target != null && target.mActivityRecord != null) { + target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false; + } } void updateImeControlTarget() { @@ -4010,10 +4165,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final SurfaceControl newParent = computeImeParent(); if (newParent != null && newParent != mInputMethodSurfaceParent) { mInputMethodSurfaceParent = newParent; - getPendingTransaction().reparent(mImeWindowsContainer.mSurfaceControl, newParent); + getSyncTransaction().reparent(mImeWindowsContainer.mSurfaceControl, newParent); // When surface parent is removed, the relative layer will also be removed. We need to // do a force update to make sure there is a layer set for the new parent. - assignRelativeLayerForIme(getPendingTransaction(), true /* forceUpdate */); + assignRelativeLayerForIme(getSyncTransaction(), true /* forceUpdate */); scheduleAnimation(); } } @@ -4038,14 +4193,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ @VisibleForTesting SurfaceControl computeImeParent() { - // Force attaching IME to the display when magnifying, or it would be magnified with - // target app together. - final boolean allowAttachToApp = (mMagnificationSpec == null); - // Attach it to app if the target is part of an app and such app is covering the entire // screen. If it's not covering the entire screen the IME might extend beyond the apps // bounds. - if (allowAttachToApp && shouldImeAttachedToApp()) { + if (shouldImeAttachedToApp()) { if (mImeLayeringTarget.mActivityRecord != mImeInputTarget.mActivityRecord) { // Do not change parent if the window hasn't requested IME. return null; @@ -4226,7 +4377,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (DEBUG_INPUT_METHOD) { Slog.i(TAG_WM, "Desired input method target: " + imFocus); Slog.i(TAG_WM, "Current focus: " + mCurrentFocus + " displayId=" + mDisplayId); - Slog.i(TAG_WM, "Last focus: " + mLastFocus + " displayId=" + mDisplayId); } if (DEBUG_INPUT_METHOD) { @@ -4351,6 +4501,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing, true /* inTraversal, must call performTraversalInTrans... below */); } + // If the display now has content, or no longer has content, update layer mirroring. + updateMirroring(); final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible(); if (wallpaperVisible != mLastWallpaperVisible) { @@ -4516,6 +4668,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } + // clear first just in case. + mTmpActivityList.clear(); // Time to remove any exiting applications? forAllRootTasks(task -> { final ArrayList<ActivityRecord> activities = task.mExitingActivities; @@ -4523,16 +4677,24 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final ActivityRecord activity = activities.get(j); if (!activity.hasVisible && !mDisplayContent.mClosingApps.contains(activity) && (!activity.mIsExiting || activity.isEmpty())) { - // Make sure there is no animation running on this activity, so any windows - // associated with it will be removed as soon as their animations are - // complete. - cancelAnimation(); - ProtoLog.v(WM_DEBUG_ADD_REMOVE, - "performLayout: Activity exiting now removed %s", activity); - activity.removeIfPossible(); + mTmpActivityList.add(activity); } } }); + if (!mTmpActivityList.isEmpty()) { + // Make sure there is no animation running on this activity, so any windows + // associated with it will be removed as soon as their animations are + // complete. + cancelAnimation(); + } + for (int i = 0; i < mTmpActivityList.size(); ++i) { + final ActivityRecord activity = mTmpActivityList.get(i); + ProtoLog.v(WM_DEBUG_ADD_REMOVE, + "performLayout: Activity exiting now removed %s", activity); + activity.removeIfPossible(); + } + // Clear afterwards so we don't hold references. + mTmpActivityList.clear(); } @Override @@ -4541,12 +4703,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.requestTraversal(); } + @Override boolean okToDisplay() { - return okToDisplay(false); - } - - boolean okToDisplay(boolean ignoreFrozen) { - return okToDisplay(ignoreFrozen, false /* ignoreScreenOn */); + return okToDisplay(false /* ignoreFrozen */, false /* ignoreScreenOn */); } boolean okToDisplay(boolean ignoreFrozen, boolean ignoreScreenOn) { @@ -4558,18 +4717,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mDisplayInfo.state == Display.STATE_ON; } - boolean okToAnimate() { - return okToAnimate(false); - } - - boolean okToAnimate(boolean ignoreFrozen) { - return okToAnimate(ignoreFrozen, false /* ignoreScreenOn */); - } - + @Override boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) { return okToDisplay(ignoreFrozen, ignoreScreenOn) && (mDisplayId != DEFAULT_DISPLAY - || mWmService.mPolicy.okToAnimate(ignoreScreenOn)); + || mWmService.mPolicy.okToAnimate(ignoreScreenOn)) + && getDisplayPolicy().isScreenOnFully(); } static final class TaskForResizePointSearchResult { @@ -4603,7 +4756,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return true; } - if (task.isOrganized()) { + // TODO(b/165794880): Freeform task organizer doesn't support drag-resize yet. Remove + // the special case when it does. + if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FREEFORM) { return true; } @@ -4700,7 +4855,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // WindowState#applyImeWindowsIfNeeded} in case of any state mismatch. return dc.mImeLayeringTarget != null && (!dc.getDefaultTaskDisplayArea().isSplitScreenModeActivated() - || dc.mImeLayeringTarget.getTask() == null); + || dc.mImeLayeringTarget.getTask() == null) + // Make sure that the IME window won't be skipped to report that it has + // completed the orientation change. + && !dc.mWmService.mDisplayFrozen; } /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */ @@ -4850,15 +5008,24 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Keep IME window in surface parent as long as app's starting window // exists so it get's layered above the starting window. if (imeTarget != null && !(imeTarget.mActivityRecord != null - && imeTarget.mActivityRecord.hasStartingWindow()) && ( - !(imeTarget.inMultiWindowMode() - || imeTarget.mToken.isAppTransitioning()) && ( - imeTarget.getSurfaceControl() != null))) { - mImeWindowsContainer.assignRelativeLayer(t, imeTarget.getSurfaceControl(), - // TODO: We need to use an extra level on the app surface to ensure - // this is always above SurfaceView but always below attached window. - 1, forceUpdate); - } else if (mInputMethodSurfaceParent != null) { + && imeTarget.mActivityRecord.hasStartingWindow())) { + final WindowToken imeControlTargetToken = + mImeControlTarget != null && mImeControlTarget.getWindow() != null + ? mImeControlTarget.getWindow().mToken : null; + final boolean canImeTargetSetRelativeLayer = imeTarget.getSurfaceControl() != null + && imeTarget.mToken == imeControlTargetToken + && !imeTarget.inMultiWindowMode() + && imeTarget.mToken.getActivity(app -> app.isAnimating(TRANSITION | PARENTS, + ANIMATION_TYPE_ALL & ~ANIMATION_TYPE_RECENTS)) == null; + if (canImeTargetSetRelativeLayer) { + mImeWindowsContainer.assignRelativeLayer(t, imeTarget.getSurfaceControl(), + // TODO: We need to use an extra level on the app surface to ensure + // this is always above SurfaceView but always below attached window. + 1, forceUpdate); + return; + } + } + if (mInputMethodSurfaceParent != null) { // The IME surface parent may not be its window parent's surface // (@see #computeImeParent), so set relative layer here instead of letting the window // parent to assign layer. @@ -4904,6 +5071,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * Increment the deferral count to determine whether to update the IME target. */ void deferUpdateImeTarget() { + if (mDeferUpdateImeTargetCount == 0) { + mUpdateImeRequestedWhileDeferred = false; + } mDeferUpdateImeTargetCount++; } @@ -4917,7 +5087,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } mDeferUpdateImeTargetCount--; - if (mDeferUpdateImeTargetCount == 0) { + if (mDeferUpdateImeTargetCount == 0 && mUpdateImeRequestedWhileDeferred) { computeImeTarget(true /* updateImeTarget */); } } @@ -4962,10 +5132,18 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } + /** + * @deprecated new transition should use {@link #requestTransitionAndLegacyPrepare(int, int)} + */ + @Deprecated void prepareAppTransition(@WindowManager.TransitionType int transit) { prepareAppTransition(transit, 0 /* flags */); } + /** + * @deprecated new transition should use {@link #requestTransitionAndLegacyPrepare(int, int)} + */ + @Deprecated void prepareAppTransition(@WindowManager.TransitionType int transit, @WindowManager.TransitionFlags int flags) { final boolean prepared = mAppTransition.prepareAppTransition(transit, flags); @@ -4978,26 +5156,27 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * Helper that both requests a transition (using the new transition system) and prepares * the legacy transition system. Use this when both systems have the same start-point. * - * @see TransitionController#requestTransitionIfNeeded(int, int, WindowContainer) + * @see TransitionController#requestTransitionIfNeeded(int, int, WindowContainer, + * WindowContainer) * @see AppTransition#prepareAppTransition */ void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit, @WindowManager.TransitionFlags int flags) { prepareAppTransition(transit, flags); - mAtmService.getTransitionController().requestTransitionIfNeeded(transit, flags, - null /* trigger */); + mTransitionController.requestTransitionIfNeeded(transit, flags, + null /* trigger */, this); } /** @see #requestTransitionAndLegacyPrepare(int, int) */ void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit, @Nullable WindowContainer trigger) { prepareAppTransition(transit); - mAtmService.getTransitionController().requestTransitionIfNeeded(transit, 0 /* flags */, - trigger); + mTransitionController.requestTransitionIfNeeded(transit, 0 /* flags */, + trigger, this); } void executeAppTransition() { - mAtmService.getTransitionController().setReady(); + mTransitionController.setReady(this); if (mAppTransition.isTransitionSet()) { ProtoLog.w(WM_DEBUG_APP_TRANSITIONS, "Execute app transition: %s, displayId: %d Callers=%s", @@ -5007,6 +5186,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } + void cancelAppTransition() { + if (!mAppTransition.isTransitionSet() || mAppTransition.isRunning()) return; + mAppTransition.abort(); + } + /** * Update pendingLayoutChanges after app transition has finished. */ @@ -5026,9 +5210,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp onAppTransitionDone(); changes |= FINISH_LAYOUT_REDO_LAYOUT; - if (DEBUG_WALLPAPER_LIGHT) { - Slog.v(TAG_WM, "Wallpaper layer changed: assigning layers + relayout"); - } + ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper layer changed: assigning layers + relayout"); computeImeTarget(true /* updateImeTarget */); mWallpaperMayChange = true; // Since the window list has been rebuilt, focus might have to be recomputed since the @@ -5040,6 +5222,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Check if pending app transition is for activity / task launch. */ boolean isNextTransitionForward() { + // TODO(b/191375840): decouple "forwardness" from transition system. + if (mTransitionController.isShellTransitionsEnabled()) { + @WindowManager.TransitionType int type = + mTransitionController.getCollectingTransitionType(); + return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT; + } return mAppTransition.containsTransitRequest(TRANSIT_OPEN) || mAppTransition.containsTransitRequest(TRANSIT_TO_FRONT); } @@ -5081,7 +5269,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } if (!mLocationInParentWindow.equals(x, y)) { mLocationInParentWindow.set(x, y); - if (mWmService.mAccessibilityController != null) { + if (mWmService.mAccessibilityController.hasCallbacks()) { mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(mDisplayId); } notifyLocationInParentDisplayChanged(); @@ -5412,17 +5600,33 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void onDisplayChanged() { mDisplay.getRealSize(mTmpDisplaySize); setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); + final int lastDisplayState = mDisplayInfo.state; updateDisplayInfo(); // The window policy is responsible for stopping activities on the default display. final int displayId = mDisplay.getDisplayId(); + final int displayState = mDisplayInfo.state; if (displayId != DEFAULT_DISPLAY) { - final int displayState = mDisplay.getState(); if (displayState == Display.STATE_OFF) { mOffTokenAcquirer.acquire(mDisplayId); } else if (displayState == Display.STATE_ON) { mOffTokenAcquirer.release(mDisplayId); } + ProtoLog.v(WM_DEBUG_LAYER_MIRRORING, + "Display %d state is now (%d), so update layer mirroring?", + mDisplayId, displayState); + if (lastDisplayState != displayState) { + // If state is on due to surface being added, then start layer mirroring. + // If state is off due to surface being removed, then stop layer mirroring. + updateMirroring(); + } + } + // Dispatch pending Configuration to WindowContext if the associated display changes to + // un-suspended state from suspended. + if (isSuspendedState(lastDisplayState) + && !isSuspendedState(displayState) && displayState != STATE_UNKNOWN) { + mWmService.mWindowContextListenerController + .dispatchPendingConfigurationIfNeeded(mDisplayId); } mWmService.requestTraversal(); } @@ -5603,6 +5807,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } mWmService.mDisplayNotificationController.dispatchDisplayChanged( this, getConfiguration()); + if (isReady() && mTransitionController.isShellTransitionsEnabled()) { + requestChangeTransitionIfNeeded(changes); + } } return changes; } @@ -5612,7 +5819,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final Configuration currOverrideConfig = getRequestedOverrideConfiguration(); final int currRotation = currOverrideConfig.windowConfiguration.getRotation(); final int overrideRotation = overrideConfiguration.windowConfiguration.getRotation(); - if (currRotation != ROTATION_UNDEFINED && currRotation != overrideRotation) { + if (currRotation != ROTATION_UNDEFINED && overrideRotation != ROTATION_UNDEFINED + && currRotation != overrideRotation) { applyRotationAndFinishFixedRotation(currRotation, overrideRotation); } mCurrentOverrideConfigurationChanges = currOverrideConfig.diff(overrideConfiguration); @@ -5623,6 +5831,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED); } + @Override + void onResize() { + super.onResize(); + if (mWmService.mAccessibilityController.hasCallbacks()) { + mWmService.mAccessibilityController.onDisplaySizeChanged(this); + } + } + /** * If the launching rotated activity ({@link #mFixedRotationLaunchingApp}) is null, it simply * applies the rotation to display. Otherwise because the activity has shown as rotated, the @@ -5703,6 +5919,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } mRemoved = true; + if (mMirroredSurface != null) { + // Do not wait for the mirrored surface to be garbage collected, but clean up + // immediately. + mWmService.mTransactionFactory.get().remove(mMirroredSurface).apply(); + mMirroredSurface = null; + } + // Only update focus/visibility for the last one because there may be many root tasks are // reparented and the intermediate states are unnecessary. if (lastReparentedRootTask != null) { @@ -5865,6 +6088,200 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return true; } + /** + * Sets if Display APIs should be sandboxed to the activity window bounds. + */ + void setSandboxDisplayApis(boolean sandboxDisplayApis) { + mSandboxDisplayApis = sandboxDisplayApis; + } + + /** + * Returns {@code true} is Display APIs should be sandboxed to the activity window bounds, + * {@code false} otherwise. Default to true, unless set for debugging purposes. + */ + boolean sandboxDisplayApis() { + return mSandboxDisplayApis; + } + + /** + * Start mirroring to this DisplayContent if it does not have its own content. Captures the + * content of a WindowContainer indicated by a WindowToken. If unable to start mirroring, falls + * back to original MediaProjection approach. + */ + private void startMirrorIfNeeded() { + // Only mirror if this display does not have its own content, is not mirroring already, + // and if this display is on (it has a surface to write output to). + if (mLastHasContent || isCurrentlyMirroring() || mDisplay.getState() == Display.STATE_OFF) { + return; + } + + // Given the WindowToken of the DisplayArea to mirror, retrieve the associated + // SurfaceControl. + IBinder tokenToMirror = mWmService.mDisplayManagerInternal.getWindowTokenClientToMirror( + mDisplayId); + if (tokenToMirror == null) { + // This DisplayContent instance is not involved in layer mirroring. If the display + // has been created for capturing, fall back to prior MediaProjection approach. + return; + } + + final WindowContainer wc = mWmService.mWindowContextListenerController.getContainer( + tokenToMirror); + if (wc == null) { + // Un-set the window token to mirror for this VirtualDisplay, to fall back to the + // original MediaProjection approach. + mWmService.mDisplayManagerInternal.setWindowTokenClientToMirror(mDisplayId, null); + ProtoLog.v(WM_DEBUG_LAYER_MIRRORING, + "Unable to retrieve window container to start layer mirroring for display %d", + mDisplayId); + return; + } + + Point surfaceSize = fetchSurfaceSizeIfPresent(); + if (surfaceSize == null) { + ProtoLog.v(WM_DEBUG_LAYER_MIRRORING, + "Unable to start layer mirroring for display %d since the surface is not " + + "available.", + mDisplayId); + return; + } + ProtoLog.v(WM_DEBUG_LAYER_MIRRORING, + "Display %d has no content and is on, so start layer mirroring for state %d", + mDisplayId, mDisplay.getState()); + + // Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture. + SurfaceControl sc = wc.getDisplayContent().getSurfaceControl(); + mMirroredSurface = SurfaceControl.mirrorSurface(sc); + SurfaceControl.Transaction transaction = mWmService.mTransactionFactory.get() + // Set the mMirroredSurface's parent to the root SurfaceControl for this + // DisplayContent. This brings the new mirrored hierarchy under this DisplayContent, + // so SurfaceControl will write the layers of this hierarchy to the output surface + // provided by the app. + .reparent(mMirroredSurface, mSurfaceControl) + // Reparent the SurfaceControl of this DisplayContent to null, to prevent content + // being added to it. This ensures that no app launched explicitly on the + // VirtualDisplay will show up as part of the mirrored content. + .reparent(mWindowingLayer, null) + .reparent(mOverlayLayer, null); + // Retrieve the size of the DisplayArea to mirror. + updateMirroredSurface(transaction, wc.getDisplayContent().getBounds(), surfaceSize); + mTokenToMirror = tokenToMirror; + + // No need to clean up. In SurfaceFlinger, parents hold references to their children. The + // mirrored SurfaceControl is alive since the parent DisplayContent SurfaceControl is + // holding a reference to it. Therefore, the mirrored SurfaceControl will be cleaned up + // when the VirtualDisplay is destroyed - which will clean up this DisplayContent. + } + + /** + * Start mirroring if this DisplayContent no longer has content. Stop mirroring if it now + * has content or the display is not on. + */ + private void updateMirroring() { + if (isCurrentlyMirroring() && (mLastHasContent + || mDisplay.getState() == Display.STATE_OFF)) { + ProtoLog.v(WM_DEBUG_LAYER_MIRRORING, + "Display %d has content (%b) so disable layer mirroring", mDisplayId, + mLastHasContent); + // If the display is not on and it is a virtual display, then it no longer has an + // associated surface to write output to. + // If the display now has content, stop mirroring to it. + mWmService.mTransactionFactory.get() + // Remove the reference to mMirroredSurface, to clean up associated memory. + .remove(mMirroredSurface) + // Reparent the SurfaceControl of this DisplayContent back to mSurfaceControl, + // to allow content to be added to it. This allows this DisplayContent to stop + // mirroring and show content normally. + .reparent(mWindowingLayer, mSurfaceControl) + .reparent(mOverlayLayer, mSurfaceControl) + .apply(); + // Stop mirroring by destroying the reference to the mirrored layer. + mMirroredSurface = null; + // Do not un-set the token, in case content is removed and mirroring should begin again. + } else { + // Display no longer has content, or now has a surface to write to, so try to start + // mirroring to it. + startMirrorIfNeeded(); + } + } + + /** + * Apply transformations to the mirrored surface to ensure the captured contents are scaled to + * fit and centred in the output surface. + * + * @param transaction the transaction to include transformations of mMirroredSurface + * to. Transaction is not applied before returning. + * @param displayAreaBounds bounds of the DisplayArea to mirror to the surface provided by + * the app. + * @param surfaceSize the default size of the surface to write the display area content to + */ + @VisibleForTesting + void updateMirroredSurface(SurfaceControl.Transaction transaction, + Rect displayAreaBounds, Point surfaceSize) { + // Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the + // output surface. + float scaleX = surfaceSize.x / (float) displayAreaBounds.width(); + float scaleY = surfaceSize.y / (float) displayAreaBounds.height(); + float scale = Math.min(scaleX, scaleY); + int scaledWidth = Math.round(scale * (float) displayAreaBounds.width()); + int scaledHeight = Math.round(scale * (float) displayAreaBounds.height()); + + // Calculate the shift to apply to the root mirror SurfaceControl to centre the mirrored + // contents in the output surface. + int shiftedX = 0; + if (scaledWidth != surfaceSize.x) { + shiftedX = (surfaceSize.x - scaledWidth) / 2; + } + int shiftedY = 0; + if (scaledHeight != surfaceSize.y) { + shiftedY = (surfaceSize.y - scaledHeight) / 2; + } + + transaction + // Crop the area to capture to exclude the 'extra' wallpaper that is used + // for parallax (b/189930234). + .setWindowCrop(mMirroredSurface, displayAreaBounds.width(), + displayAreaBounds.height()) + // Scale the root mirror SurfaceControl, based upon the size difference between the + // source (DisplayArea to capture) and output (surface the app reads images from). + .setMatrix(mMirroredSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale) + // Position needs to be updated when the mirrored DisplayArea has changed, since + // the content will no longer be centered in the output surface. + .setPosition(mMirroredSurface, shiftedX /* x */, shiftedY /* y */) + .apply(); + mLastMirroredDisplayAreaBounds = new Rect(displayAreaBounds); + } + + /** + * Returns a non-null {@link Point} if the surface is present, or null otherwise + */ + Point fetchSurfaceSizeIfPresent() { + // Retrieve the default size of the surface the app provided to + // MediaProjection#createVirtualDisplay. Note the app is the consumer of the surface, + // since it reads out buffers from the surface, and SurfaceFlinger is the producer since + // it writes the mirrored layers to the buffers. + Point surfaceSize = mWmService.mDisplayManagerInternal.getDisplaySurfaceDefaultSize( + mDisplayId); + if (surfaceSize == null) { + // Layer mirroring started with a null surface, so do not apply any transformations yet. + // State of virtual display will change to 'ON' when the surface is set. + // will get event DISPLAY_DEVICE_EVENT_CHANGED + ProtoLog.v(WM_DEBUG_LAYER_MIRRORING, + "Provided surface for layer mirroring on display %d is not present, so do not" + + " update the surface", + mDisplayId); + return null; + } + return surfaceSize; + } + + /** + * Returns {@code true} if this DisplayContent is currently layer mirroring. + */ + boolean isCurrentlyMirroring() { + return mTokenToMirror != null && mMirroredSurface != null; + } + /** The entry for proceeding to handle {@link #mFixedRotationLaunchingApp}. */ class FixedRotationTransitionListener extends WindowManagerInternal.AppTransitionListener { @@ -6009,7 +6426,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp class RemoteInsetsControlTarget implements InsetsControlTarget { private final IDisplayWindowInsetsController mRemoteInsetsController; - private final InsetsState mRequestedInsetsState = new InsetsState(); + private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); RemoteInsetsControlTarget(IDisplayWindowInsetsController controller) { mRemoteInsetsController = controller; @@ -6071,15 +6488,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (type == ITYPE_IME) { return getInsetsStateController().getImeSourceProvider().isImeShowing(); } - return mRequestedInsetsState.getSourceOrDefaultVisibility(type); + return mRequestedVisibilities.getVisibility(type); } - void updateRequestedVisibility(InsetsState state) { - for (int i = 0; i < InsetsState.SIZE; i++) { - final InsetsSource source = state.peekSource(i); - if (source == null) continue; - mRequestedInsetsState.addSource(source); - } + void setRequestedVisibilities(InsetsVisibilities requestedVisibilities) { + mRequestedVisibilities.set(requestedVisibilities); } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 03b5478421cf..c1231da1ef93 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -16,13 +16,10 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.res.Configuration.UI_MODE_TYPE_CAR; import static android.content.res.Configuration.UI_MODE_TYPE_MASK; @@ -41,6 +38,7 @@ import static android.view.InsetsState.ITYPE_RIGHT_GESTURES; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES; import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT; +import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION; import static android.view.ViewRootImpl.computeWindowBounds; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; @@ -126,8 +124,10 @@ import android.content.res.Resources; import android.content.pm.ApplicationInfo; import android.graphics.Insets; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.gui.DropInputMode; import android.hardware.power.Boost; import android.os.Handler; import android.os.IBinder; @@ -136,6 +136,7 @@ import android.os.Message; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; +import android.util.ArraySet; import android.util.BoostFramework; import android.util.PrintWriterPrinter; import android.util.Slog; @@ -147,6 +148,7 @@ import android.view.InsetsFlags; import android.view.InsetsSource; import android.view.InsetsState; import android.view.InsetsState.InternalInsetsType; +import android.view.InsetsVisibilities; import android.view.Surface; import android.view.View; import android.view.ViewDebug; @@ -162,6 +164,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.GestureNavigationSettingsObserver; import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.internal.policy.SystemBarUtils; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ScreenshotHelper; import com.android.internal.util.function.TriConsumer; @@ -178,6 +181,9 @@ import com.android.server.wallpaper.WallpaperManagerInternal; import com.android.server.wm.InputMonitor.EventReceiverInputConsumer; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Objects; import java.util.function.Consumer; /** @@ -331,17 +337,41 @@ public class DisplayPolicy { private WindowState mSystemUiControllingWindow; + // Candidate window to determine the color of navigation bar. The window needs to be top + // fullscreen-app windows or dim layers that are intersecting with the window frame of status + // bar. + private WindowState mNavBarColorWindowCandidate; + + // The window to determine opacity and background of translucent navigation bar. The window + // needs to be opaque. + private WindowState mNavBarBackgroundWindow; + + /** + * Windows to determine the color of status bar. See {@link #mNavBarColorWindowCandidate} for + * the conditions of being candidate window. + */ + private final ArrayList<WindowState> mStatusBarColorWindows = new ArrayList<>(); + + /** + * Windows to determine opacity and background of translucent status bar. The window needs to be + * opaque + */ + private final ArrayList<WindowState> mStatusBarBackgroundWindows = new ArrayList<>(); + + private String mFocusedApp; private int mLastDisableFlags; private int mLastAppearance; - private int mLastFullscreenAppearance; - private int mLastDockedAppearance; private int mLastBehavior; - private final Rect mNonDockedRootTaskBounds = new Rect(); - private final Rect mDockedRootTaskBounds = new Rect(); - private final Rect mLastNonDockedRootTaskBounds = new Rect(); - private final Rect mLastDockedRootTaskBounds = new Rect(); + private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); + private AppearanceRegion[] mLastStatusBarAppearanceRegions; - // What we last reported to system UI about whether the focused window is fullscreen/immersive. + /** The union of checked bounds while fetching {@link #mStatusBarColorWindows}. */ + private final Rect mStatusBarColorCheckedBounds = new Rect(); + + /** The union of checked bounds while fetching {@link #mStatusBarBackgroundWindows}. */ + private final Rect mStatusBarBackgroundCheckedBounds = new Rect(); + + // What we last reported to input dispatcher about whether the focused window is fullscreen. private boolean mLastFocusIsFullscreen = false; // If nonzero, a panic gesture was performed at that time in uptime millis and is still pending. @@ -351,19 +381,15 @@ public class DisplayPolicy { private static final Rect sTmpRect = new Rect(); private static final Rect sTmpNavFrame = new Rect(); private static final Rect sTmpStatusFrame = new Rect(); + private static final Rect sTmpDecorFrame = new Rect(); private static final Rect sTmpScreenDecorFrame = new Rect(); private static final Rect sTmpLastParentFrame = new Rect(); private static final Rect sTmpDisplayFrameBounds = new Rect(); private WindowState mTopFullscreenOpaqueWindowState; - private WindowState mTopFullscreenOpaqueOrDimmingWindowState; - private WindowState mTopDockedOpaqueWindowState; - private WindowState mTopDockedOpaqueOrDimmingWindowState; private boolean mTopIsFullscreen; private boolean mForceStatusBar; private int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED; - private boolean mForcingShowNavBar; - private int mForcingShowNavBarLayer; private boolean mForceShowSystemBars; private boolean mShowingDream; @@ -421,7 +447,7 @@ public class DisplayPolicy { WindowState targetBar = (msg.arg1 == MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS) ? getStatusBar() : getNavigationBar(); if (targetBar != null) { - requestTransientBars(targetBar); + requestTransientBars(targetBar, true /* isGestureOnSystemBar */); } } break; @@ -482,8 +508,10 @@ public class DisplayPolicy { final int displayId = displayContent.getDisplayId(); - mBarContentFrames.put(TYPE_STATUS_BAR, new Rect()); - mBarContentFrames.put(TYPE_NAVIGATION_BAR, new Rect()); + if (!INSETS_LAYOUT_GENERALIZATION) { + mBarContentFrames.put(TYPE_STATUS_BAR, new Rect()); + mBarContentFrames.put(TYPE_NAVIGATION_BAR, new Rect()); + } final Resources r = mContext.getResources(); mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer); @@ -508,24 +536,25 @@ public class DisplayPolicy { // TODO(b/181821798) Migrate SystemGesturesPointerEventListener to use window context. mSystemGestures = new SystemGesturesPointerEventListener(mUiContext, mHandler, new SystemGesturesPointerEventListener.Callbacks() { + @Override public void onSwipeFromTop() { synchronized (mLock) { - if (mStatusBar != null) { - requestTransientBars(mStatusBar); - } - checkAltBarSwipeForTransientBars(ALT_BAR_TOP); + final WindowState bar = mStatusBar != null + ? mStatusBar + : findAltBarMatchingPosition(ALT_BAR_TOP); + requestTransientBars(bar, true /* isGestureOnSystemBar */); } } @Override public void onSwipeFromBottom() { synchronized (mLock) { - if (mNavigationBar != null - && mNavigationBarPosition == NAV_BAR_BOTTOM) { - requestTransientBars(mNavigationBar); - } - checkAltBarSwipeForTransientBars(ALT_BAR_BOTTOM); + final WindowState bar = mNavigationBar != null + && mNavigationBarPosition == NAV_BAR_BOTTOM + ? mNavigationBar + : findAltBarMatchingPosition(ALT_BAR_BOTTOM); + requestTransientBars(bar, true /* isGestureOnSystemBar */); } } @@ -535,13 +564,8 @@ public class DisplayPolicy { synchronized (mLock) { mDisplayContent.calculateSystemGestureExclusion( excludedRegion, null /* outUnrestricted */); - final boolean excluded = - mSystemGestures.currentGestureStartedInRegion(excludedRegion); - if (mNavigationBar != null && (mNavigationBarPosition == NAV_BAR_RIGHT - || !excluded && mNavigationBarAlwaysShowOnSideGesture)) { - requestTransientBars(mNavigationBar); - } - checkAltBarSwipeForTransientBars(ALT_BAR_RIGHT); + requestTransientBarsForSideSwipe(excludedRegion, NAV_BAR_RIGHT, + ALT_BAR_RIGHT); } excludedRegion.recycle(); } @@ -552,17 +576,33 @@ public class DisplayPolicy { synchronized (mLock) { mDisplayContent.calculateSystemGestureExclusion( excludedRegion, null /* outUnrestricted */); - final boolean excluded = - mSystemGestures.currentGestureStartedInRegion(excludedRegion); - if (mNavigationBar != null && (mNavigationBarPosition == NAV_BAR_LEFT - || !excluded && mNavigationBarAlwaysShowOnSideGesture)) { - requestTransientBars(mNavigationBar); - } - checkAltBarSwipeForTransientBars(ALT_BAR_LEFT); + requestTransientBarsForSideSwipe(excludedRegion, NAV_BAR_LEFT, + ALT_BAR_LEFT); } excludedRegion.recycle(); } + private void requestTransientBarsForSideSwipe(Region excludedRegion, + int navBarSide, int altBarSide) { + final WindowState barMatchingSide = mNavigationBar != null + && mNavigationBarPosition == navBarSide + ? mNavigationBar + : findAltBarMatchingPosition(altBarSide); + final boolean allowSideSwipe = mNavigationBarAlwaysShowOnSideGesture && + !mSystemGestures.currentGestureStartedInRegion(excludedRegion); + if (barMatchingSide == null && !allowSideSwipe) { + return; + } + + // Request transient bars on the matching bar, or any bar if we always allow + // side swipes to show the bars + final boolean isGestureOnSystemBar = barMatchingSide != null; + final WindowState bar = barMatchingSide != null + ? barMatchingSide + : findTransientNavOrAltBar(); + requestTransientBars(bar, isGestureOnSystemBar); + } + @Override public void onFling(int duration) { if (mService.mPowerManagerInternal != null) { @@ -747,7 +787,8 @@ public class DisplayPolicy { } @Override - public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration, + public int onAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) { mHandler.post(() -> { StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); @@ -770,6 +811,7 @@ public class DisplayPolicy { } }; displayContent.mAppTransition.registerListenerLocked(mAppTransitionListener); + displayContent.mTransitionController.registerLegacyListener(mAppTransitionListener); mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext, looper, mService.mVrModeEnabled); @@ -809,19 +851,39 @@ public class DisplayPolicy { mHandler.post(mGestureNavigationSettingsObserver::register); } - private void checkAltBarSwipeForTransientBars(@WindowManagerPolicy.AltBarPosition int pos) { + /** + * Returns the first non-null alt bar window matching the given position. + */ + private WindowState findAltBarMatchingPosition(@WindowManagerPolicy.AltBarPosition int pos) { if (mStatusBarAlt != null && mStatusBarAltPosition == pos) { - requestTransientBars(mStatusBarAlt); + return mStatusBarAlt; } if (mNavigationBarAlt != null && mNavigationBarAltPosition == pos) { - requestTransientBars(mNavigationBarAlt); + return mNavigationBarAlt; } if (mClimateBarAlt != null && mClimateBarAltPosition == pos) { - requestTransientBars(mClimateBarAlt); + return mClimateBarAlt; } if (mExtraNavBarAlt != null && mExtraNavBarAltPosition == pos) { - requestTransientBars(mExtraNavBarAlt); + return mExtraNavBarAlt; + } + return null; + } + + /** + * Finds the first non-null nav bar to request transient for. + */ + private WindowState findTransientNavOrAltBar() { + if (mNavigationBar != null) { + return mNavigationBar; } + if (mNavigationBarAlt != null) { + return mNavigationBarAlt; + } + if (mExtraNavBarAlt != null) { + return mExtraNavBarAlt; + } + return null; } void systemReady() { @@ -1024,15 +1086,6 @@ public class DisplayPolicy { // letterboxed. Hence always let them extend under the cutout. attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; break; - case TYPE_NOTIFICATION_SHADE: - // If the Keyguard is in a hidden state (occluded by another window), we force to - // remove the wallpaper and keyguard flag so that any change in-flight after setting - // the keyguard as occluded wouldn't set these flags again. - // See {@link #processKeyguardSetHiddenResultLw}. - if (mService.mPolicy.isKeyguardOccluded()) { - attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; - } - break; case TYPE_TOAST: // While apps should use the dedicated toast APIs to add such windows @@ -1085,6 +1138,20 @@ public class DisplayPolicy { } /** + * Add additional policy if needed to ensure the window or its children should not receive any + * input. + */ + public void setDropInputModePolicy(WindowState win, LayoutParams attrs) { + if (attrs.type == TYPE_TOAST + && (attrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) == 0) { + // Toasts should not receive input. These windows should not have any children, so + // force this hierarchy of windows to drop all input. + mService.mTransactionFactory.get() + .setDropInputMode(win.getSurfaceControl(), DropInputMode.ALL).apply(); + } + } + + /** * Check if a window can be added to the system. * * Currently enforces that two window types are singletons per display: @@ -1228,15 +1295,14 @@ public class DisplayPolicy { switch (attrs.type) { case TYPE_NOTIFICATION_SHADE: mNotificationShade = win; - if (mDisplayContent.isDefaultDisplay) { - mService.mPolicy.setKeyguardCandidateLw(win); - } break; case TYPE_STATUS_BAR: mStatusBar = win; final TriConsumer<DisplayFrames, WindowState, Rect> frameProvider = (displayFrames, windowState, rect) -> { - rect.bottom = rect.top + getStatusBarHeight(displayFrames); + if (!INSETS_LAYOUT_GENERALIZATION) { + rect.bottom = rect.top + getStatusBarHeight(displayFrames); + } }; final TriConsumer<DisplayFrames, WindowState, Rect> gestureFrameProvider = (displayFrames, windowState, rect) -> { @@ -1259,18 +1325,22 @@ public class DisplayPolicy { mNavigationBar = win; mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, win, (displayFrames, windowState, inOutFrame) -> { - - // In Gesture Nav, navigation bar frame is larger than frame to - // calculate inset. - if (navigationBarPosition(displayFrames.mDisplayWidth, - displayFrames.mDisplayHeight, - displayFrames.mRotation) == NAV_BAR_BOTTOM - && !mNavButtonForcedVisible) { - sTmpRect.set(inOutFrame); - sTmpRect.intersectUnchecked(displayFrames.mDisplayCutoutSafe); - inOutFrame.top = sTmpRect.bottom - - getNavigationBarHeight(displayFrames.mRotation, - mDisplayContent.getConfiguration().uiMode); + if (INSETS_LAYOUT_GENERALIZATION) { + inOutFrame.inset(windowState.getLayoutingAttrs( + displayFrames.mRotation).providedInternalInsets); + } else { + // In Gesture Nav, navigation bar frame is larger than frame to + // calculate inset. + if (navigationBarPosition(displayFrames.mDisplayWidth, + displayFrames.mDisplayHeight, + displayFrames.mRotation) == NAV_BAR_BOTTOM + && !mNavButtonForcedVisible) { + sTmpRect.set(inOutFrame); + sTmpRect.intersectUnchecked(displayFrames.mDisplayCutoutSafe); + inOutFrame.top = sTmpRect.bottom + - getNavigationBarHeight(displayFrames.mRotation, + mDisplayContent.getConfiguration().uiMode); + } } }, @@ -1313,6 +1383,12 @@ public class DisplayPolicy { default: if (attrs.providesInsetsTypes != null) { for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) { + final TriConsumer<DisplayFrames, WindowState, Rect> imeFrameProvider = + !attrs.providedInternalImeInsets.equals(Insets.NONE) + ? (displayFrames, windowState, inOutFrame) -> + inOutFrame.inset(windowState.getLayoutingAttrs( + displayFrames.mRotation).providedInternalImeInsets) + : null; switch (insetsType) { case ITYPE_STATUS_BAR: mStatusBarAlt = win; @@ -1331,7 +1407,15 @@ public class DisplayPolicy { mExtraNavBarAltPosition = getAltBarPosition(attrs); break; } - mDisplayContent.setInsetProvider(insetsType, win, null); + if (!INSETS_LAYOUT_GENERALIZATION) { + mDisplayContent.setInsetProvider(insetsType, win, null, + imeFrameProvider); + } else { + mDisplayContent.setInsetProvider(insetsType, win, (displayFrames, + windowState, inOutFrame) -> inOutFrame.inset( + windowState.getLayoutingAttrs(displayFrames.mRotation) + .providedInternalInsets), imeFrameProvider); + } } } break; @@ -1406,9 +1490,6 @@ public class DisplayPolicy { mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, null, null); } else if (mNotificationShade == win) { mNotificationShade = null; - if (mDisplayContent.isDefaultDisplay) { - mService.mPolicy.setKeyguardCandidateLw(null); - } } else if (mClimateBarAlt == win) { mClimateBarAlt = null; mDisplayContent.setInsetProvider(ITYPE_CLIMATE_BAR, null, null); @@ -1422,8 +1503,22 @@ public class DisplayPolicy { } private int getStatusBarHeight(DisplayFrames displayFrames) { - return Math.max(mStatusBarHeightForRotation[displayFrames.mRotation], - displayFrames.mDisplayCutoutSafe.top); + int statusBarHeight; + if (INSETS_LAYOUT_GENERALIZATION) { + if (mStatusBar != null) { + statusBarHeight = mStatusBar.getLayoutingAttrs(displayFrames.mRotation).height; + } else { + statusBarHeight = 0; + } + } else { + statusBarHeight = mStatusBarHeightForRotation[displayFrames.mRotation]; + } + return Math.max(statusBarHeight, displayFrames.mDisplayCutoutSafe.top); + } + + @VisibleForTesting + int getStatusBarHeightForRotation(@Surface.Rotation int rotation) { + return SystemBarUtils.getStatusBarHeightForRotation(mUiContext, rotation); } WindowState getStatusBar() { @@ -1558,7 +1653,7 @@ public class DisplayPolicy { /** * @return true if the system bars are forced to stay visible */ - public boolean areSystemBarsForcedShownLw(WindowState windowState) { + public boolean areSystemBarsForcedShownLw() { return mForceShowSystemBars; } @@ -1593,13 +1688,30 @@ public class DisplayPolicy { WindowFrames simulatedWindowFrames, SparseArray<Rect> contentFrames, Consumer<Rect> layout) { win.setSimulatedWindowFrames(simulatedWindowFrames); + final int requestedHeight = win.mRequestedHeight; + final int requestedWidth = win.mRequestedWidth; + if (INSETS_LAYOUT_GENERALIZATION) { + // Without a full layout process, in order to layout the system bars correctly, we need + // to set the requested size and the initial display frames to the window. + WindowManager.LayoutParams params = win.getLayoutingAttrs(displayFrames.mRotation); + win.setRequestedSize(params.width, params.height); + sTmpDecorFrame.set(0, 0, displayFrames.mDisplayWidth, displayFrames.mDisplayHeight); + simulatedWindowFrames.setFrames(sTmpDecorFrame /* parentFrame */, + sTmpDecorFrame /* displayFrame */); + simulatedWindowFrames.mIsSimulatingDecorWindow = true; + } final Rect contentFrame = new Rect(); try { layout.accept(contentFrame); } finally { win.setSimulatedWindowFrames(null); + if (INSETS_LAYOUT_GENERALIZATION) { + win.setRequestedSize(requestedWidth, requestedHeight); + } + } + if (!INSETS_LAYOUT_GENERALIZATION) { + contentFrames.put(win.mAttrs.type, contentFrame); } - contentFrames.put(win.mAttrs.type, contentFrame); mDisplayContent.getInsetsStateController().computeSimulatedState( win, displayFrames, simulatedWindowFrames); } @@ -1610,15 +1722,51 @@ public class DisplayPolicy { * some temporal states, but doesn't change the window frames used to show on screen. */ void simulateLayoutDisplay(DisplayFrames displayFrames, SparseArray<Rect> barContentFrames) { - final WindowFrames simulatedWindowFrames = new WindowFrames(); - if (mNavigationBar != null) { - simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames, - barContentFrames, contentFrame -> layoutNavigationBar(displayFrames, - contentFrame)); - } - if (mStatusBar != null) { - simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames, - barContentFrames, contentFrame -> layoutStatusBar(displayFrames, contentFrame)); + if (INSETS_LAYOUT_GENERALIZATION) { + final InsetsStateController insetsStateController = + mDisplayContent.getInsetsStateController(); + for (int type = 0; type < InsetsState.SIZE; type++) { + final InsetsSourceProvider provider = + insetsStateController.peekSourceProvider(type); + if (provider == null || !provider.hasWindow() + || provider.mWin.getControllableInsetProvider() != provider) { + continue; + } + final WindowFrames simulatedWindowFrames = new WindowFrames(); + simulateLayoutDecorWindow(provider.mWin, displayFrames, simulatedWindowFrames, + barContentFrames, + contentFrame -> simulateLayoutForContentFrame(displayFrames, + provider.mWin, contentFrame)); + } + } else { + if (mNavigationBar != null) { + final WindowFrames simulatedWindowFrames = new WindowFrames(); + simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames, + barContentFrames, contentFrame -> layoutNavigationBar(displayFrames, + contentFrame)); + } + if (mStatusBar != null) { + final WindowFrames simulatedWindowFrames = new WindowFrames(); + simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames, + barContentFrames, + contentFrame -> layoutStatusBar(displayFrames, contentFrame)); + } + if (mExtraNavBarAlt != null) { + // There's no pre-defined behavior for the extra navigation bar, we need to use the + // new flexible insets logic anyway. + final WindowFrames simulatedWindowFrames = new WindowFrames(); + simulateLayoutDecorWindow(mExtraNavBarAlt, displayFrames, simulatedWindowFrames, + barContentFrames, + contentFrame -> simulateLayoutForContentFrame(displayFrames, + mExtraNavBarAlt, contentFrame)); + } + if (mClimateBarAlt != null) { + final WindowFrames simulatedWindowFrames = new WindowFrames(); + simulateLayoutDecorWindow(mClimateBarAlt, displayFrames, simulatedWindowFrames, + barContentFrames, + contentFrame -> simulateLayoutForContentFrame(displayFrames, + mClimateBarAlt, contentFrame)); + } } } @@ -1637,13 +1785,11 @@ public class DisplayPolicy { windowFrames.setFrames(sTmpStatusFrame /* parentFrame */, sTmpStatusFrame /* displayFrame */); // Let the status bar determine its size. - mStatusBar.computeFrameAndUpdateSourceFrame(); + mStatusBar.computeFrameAndUpdateSourceFrame(displayFrames); // For layout, the status bar is always at the top with our fixed height. int statusBarBottom = displayFrames.mUnrestricted.top + mStatusBarHeightForRotation[displayFrames.mRotation]; - // Make sure the status bar covers the entire cutout height - statusBarBottom = Math.max(statusBarBottom, displayFrames.mDisplayCutoutSafe.top); if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) { // Make sure that the zone we're avoiding for the cutout is at least as tall as the @@ -1688,18 +1834,18 @@ public class DisplayPolicy { } else if (navBarPosition == NAV_BAR_RIGHT) { // Landscape screen; nav bar goes to the right. navigationFrame.left = Math.min(cutoutSafeUnrestricted.right, navigationFrame.right) - - getNavigationBarWidth(rotation, uiMode); + - getNavigationBarWidth(rotation, uiMode, navBarPosition); } else if (navBarPosition == NAV_BAR_LEFT) { // Seascape screen; nav bar goes to the left. navigationFrame.right = Math.max(cutoutSafeUnrestricted.left, navigationFrame.left) - + getNavigationBarWidth(rotation, uiMode); + + getNavigationBarWidth(rotation, uiMode, navBarPosition); } // Compute the final frame. final WindowFrames windowFrames = mNavigationBar.getLayoutingWindowFrames(); windowFrames.setFrames(navigationFrame /* parentFrame */, navigationFrame /* displayFrame */); - mNavigationBar.computeFrameAndUpdateSourceFrame(); + mNavigationBar.computeFrameAndUpdateSourceFrame(displayFrames); sTmpRect.set(windowFrames.mFrame); sTmpRect.intersect(displayFrames.mDisplayCutoutSafe); contentFrame.set(sTmpRect); @@ -1708,6 +1854,16 @@ public class DisplayPolicy { return navBarPosition; } + private void simulateLayoutForContentFrame(DisplayFrames displayFrames, WindowState win, + Rect simulatedContentFrame) { + layoutWindowLw(win, null /* attached */, displayFrames); + final Rect contentFrame = sTmpRect; + contentFrame.set(win.getLayoutingWindowFrames().mFrame); + // Excluding the display cutout before set to the simulated content frame. + contentFrame.intersect(displayFrames.mDisplayCutoutSafe); + simulatedContentFrame.set(contentFrame); + } + private boolean canReceiveInput(WindowState win) { boolean notFocusable = (win.getAttrs().flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0; @@ -1729,12 +1885,12 @@ public class DisplayPolicy { * @param displayFrames The display frames. */ public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) { - if (win == mNavigationBar) { + if (win == mNavigationBar && !INSETS_LAYOUT_GENERALIZATION) { mNavigationBarPosition = layoutNavigationBar(displayFrames, mBarContentFrames.get(TYPE_NAVIGATION_BAR)); return; } - if ((win == mStatusBar && !canReceiveInput(win))) { + if ((win == mStatusBar && !canReceiveInput(win)) && !INSETS_LAYOUT_GENERALIZATION) { layoutStatusBar(displayFrames, mBarContentFrames.get(TYPE_STATUS_BAR)); return; } @@ -1742,7 +1898,7 @@ public class DisplayPolicy { // Skip layout of the window when in transition to pip mode. return; } - final WindowManager.LayoutParams attrs = win.getAttrs(); + final WindowManager.LayoutParams attrs = win.getLayoutingAttrs(displayFrames.mRotation); final int type = attrs.type; final int fl = attrs.flags; @@ -1750,7 +1906,7 @@ public class DisplayPolicy { final int sim = attrs.softInputMode; displayFrames = win.getDisplayFrames(displayFrames); - final WindowFrames windowFrames = win.getWindowFrames(); + final WindowFrames windowFrames = win.getLayoutingWindowFrames(); sTmpLastParentFrame.set(windowFrames.mParentFrame); final Rect pf = windowFrames.mParentFrame; @@ -1761,7 +1917,13 @@ public class DisplayPolicy { final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR; final InsetsState state = win.getInsetsState(); - computeWindowBounds(attrs, state, win.mToken.getBounds(), df); + if (windowFrames.mIsSimulatingDecorWindow && INSETS_LAYOUT_GENERALIZATION) { + // Override the bounds in window token has many side effects. Directly use the display + // frame set for the simulated layout for this case. + computeWindowBounds(attrs, state, df, df); + } else { + computeWindowBounds(attrs, state, win.getBounds(), df); + } if (attached == null) { pf.set(df); if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) { @@ -1861,7 +2023,17 @@ public class DisplayPolicy { windowFrames.setContentChanged(true); } - win.computeFrameAndUpdateSourceFrame(); + win.computeFrameAndUpdateSourceFrame(displayFrames); + if (INSETS_LAYOUT_GENERALIZATION && attrs.type == TYPE_STATUS_BAR) { + if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) { + // Make sure that the zone we're avoiding for the cutout is at least as tall as the + // status bar; otherwise fullscreen apps will end up cutting halfway into the status + // bar. + displayFrames.mDisplayCutoutSafe.top = Math.max( + displayFrames.mDisplayCutoutSafe.top, + windowFrames.mFrame.bottom); + } + } } WindowState getTopFullscreenOpaqueWindow() { @@ -1877,12 +2049,13 @@ public class DisplayPolicy { */ public void beginPostLayoutPolicyLw() { mTopFullscreenOpaqueWindowState = null; - mTopFullscreenOpaqueOrDimmingWindowState = null; - mTopDockedOpaqueWindowState = null; - mTopDockedOpaqueOrDimmingWindowState = null; + mNavBarColorWindowCandidate = null; + mNavBarBackgroundWindow = null; + mStatusBarColorWindows.clear(); + mStatusBarBackgroundWindows.clear(); + mStatusBarColorCheckedBounds.setEmpty(); + mStatusBarBackgroundCheckedBounds.setEmpty(); mForceStatusBar = false; - mForcingShowNavBar = false; - mForcingShowNavBarLayer = -1; mAllowLockscreenWhenOn = false; mShowingDream = false; @@ -1901,20 +2074,22 @@ public class DisplayPolicy { final boolean affectsSystemUi = win.canAffectSystemUiFlags(); if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": affectsSystemUi=" + affectsSystemUi); applyKeyguardPolicy(win, imeTarget); - final int fl = attrs.flags; - if (mTopFullscreenOpaqueWindowState == null && affectsSystemUi - && attrs.type == TYPE_INPUT_METHOD) { - mForcingShowNavBar = true; - mForcingShowNavBarLayer = win.getSurfaceLayer(); + + // Check if the freeform window overlaps with the navigation bar area. + final boolean isOverlappingWithNavBar = isOverlappingWithNavBar(win); + if (isOverlappingWithNavBar && !mIsFreeformWindowOverlappingWithNavBar + && win.inFreeformWindowingMode()) { + mIsFreeformWindowOverlappingWithNavBar = true; + } + + if (!affectsSystemUi) { + return; } boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW && attrs.type < FIRST_SYSTEM_WINDOW; - final int windowingMode = win.getWindowingMode(); - final boolean inFullScreenOrSplitScreenSecondaryWindowingMode = - windowingMode == WINDOWING_MODE_FULLSCREEN - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; - if (mTopFullscreenOpaqueWindowState == null && affectsSystemUi) { + if (mTopFullscreenOpaqueWindowState == null) { + final int fl = attrs.flags; if ((fl & FLAG_FORCE_NOT_FULLSCREEN) != 0) { mForceStatusBar = true; } @@ -1927,68 +2102,60 @@ public class DisplayPolicy { } } - // For app windows that are not attached, we decide if all windows in the app they - // represent should be hidden or if we should hide the lockscreen. For attached app - // windows we defer the decision to the window it is attached to. - if (appWindow && attached == null) { - if (attrs.isFullscreen() && inFullScreenOrSplitScreenSecondaryWindowingMode) { - if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win); - mTopFullscreenOpaqueWindowState = win; - if (mTopFullscreenOpaqueOrDimmingWindowState == null) { - mTopFullscreenOpaqueOrDimmingWindowState = win; - } - if ((fl & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) { - mAllowLockscreenWhenOn = true; - } - } + if (appWindow && attached == null && attrs.isFullscreen() + && (fl & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) { + mAllowLockscreenWhenOn = true; } } - // Voice interaction overrides both top fullscreen and top docked. - if (affectsSystemUi && attrs.type == TYPE_VOICE_INTERACTION && attrs.isFullscreen()) { + // Check the windows that overlap with system bars to determine system bars' appearance. + if ((appWindow && attached == null && attrs.isFullscreen()) + || attrs.type == TYPE_VOICE_INTERACTION) { + // Record the top-fullscreen-app-window which will be used to determine system UI + // controlling window. if (mTopFullscreenOpaqueWindowState == null) { mTopFullscreenOpaqueWindowState = win; - if (mTopFullscreenOpaqueOrDimmingWindowState == null) { - mTopFullscreenOpaqueOrDimmingWindowState = win; - } } - if (mTopDockedOpaqueWindowState == null) { - mTopDockedOpaqueWindowState = win; - if (mTopDockedOpaqueOrDimmingWindowState == null) { - mTopDockedOpaqueOrDimmingWindowState = win; + + // Cache app windows that is overlapping with the status bar to determine appearance + // of status bar. + if (mStatusBar != null + && sTmpRect.setIntersect(win.getFrame(), mStatusBar.getFrame()) + && !mStatusBarBackgroundCheckedBounds.contains(sTmpRect)) { + mStatusBarBackgroundWindows.add(win); + mStatusBarBackgroundCheckedBounds.union(sTmpRect); + if (!mStatusBarColorCheckedBounds.contains(sTmpRect)) { + mStatusBarColorWindows.add(win); + mStatusBarColorCheckedBounds.union(sTmpRect); } } - } - - // Keep track of the window if it's dimming but not necessarily fullscreen. - if (mTopFullscreenOpaqueOrDimmingWindowState == null && affectsSystemUi - && win.isDimming() && inFullScreenOrSplitScreenSecondaryWindowingMode) { - mTopFullscreenOpaqueOrDimmingWindowState = win; - } - // We need to keep track of the top "fullscreen" opaque window for the docked root task - // separately, because both the "real fullscreen" opaque window and the one for the docked - // root task can control View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR. - if (mTopDockedOpaqueWindowState == null && affectsSystemUi && appWindow && attached == null - && attrs.isFullscreen() && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - mTopDockedOpaqueWindowState = win; - if (mTopDockedOpaqueOrDimmingWindowState == null) { - mTopDockedOpaqueOrDimmingWindowState = win; + // Cache app window that overlaps with the navigation bar area to determine opacity + // and appearance of the navigation bar. We only need to cache one window because + // there should be only one overlapping window if it's not in gesture navigation + // mode; if it's in gesture navigation mode, the navigation bar will be + // NAV_BAR_FORCE_TRANSPARENT and its appearance won't be decided by overlapping + // windows. + if (isOverlappingWithNavBar) { + if (mNavBarColorWindowCandidate == null) { + mNavBarColorWindowCandidate = win; + } + if (mNavBarBackgroundWindow == null) { + mNavBarBackgroundWindow = win; + } + } + } else if (win.isDimming()) { + // For dimming window whose host bounds is overlapping with system bars, it can be + // used to determine colors but not opacity of system bars. + if (mStatusBar != null + && sTmpRect.setIntersect(win.getBounds(), mStatusBar.getFrame()) + && !mStatusBarColorCheckedBounds.contains(sTmpRect)) { + mStatusBarColorWindows.add(win); + mStatusBarColorCheckedBounds.union(sTmpRect); + } + if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) { + mNavBarColorWindowCandidate = win; } - } - - // Check if the freeform window overlaps with the navigation bar area. - final WindowState navBarWin = hasNavigationBar() ? mNavigationBar : null; - if (!mIsFreeformWindowOverlappingWithNavBar && win.inFreeformWindowingMode() - && isOverlappingWithNavBar(win, navBarWin)) { - mIsFreeformWindowOverlappingWithNavBar = true; - } - - // Also keep track of any windows that are dimming but not necessarily fullscreen in the - // docked root task. - if (mTopDockedOpaqueOrDimmingWindowState == null && affectsSystemUi && win.isDimming() - && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - mTopDockedOpaqueOrDimmingWindowState = win; } } @@ -2052,11 +2219,7 @@ public class DisplayPolicy { mTopIsFullscreen = topIsFullscreen; } - if (updateSystemUiVisibilityLw()) { - // If the navigation bar has been hidden or shown, we need to do another - // layout pass to update that window. - changes |= FINISH_LAYOUT_REDO_LAYOUT; - } + updateSystemBarAttributes(); if (mShowingDream != mLastShowingDream) { mLastShowingDream = mShowingDream; @@ -2159,10 +2322,11 @@ public class DisplayPolicy { if (hasStatusBar()) { mStatusBarHeightForRotation[portraitRotation] = mStatusBarHeightForRotation[upsideDownRotation] = - res.getDimensionPixelSize(R.dimen.status_bar_height_portrait); + getStatusBarHeightForRotation(portraitRotation); mStatusBarHeightForRotation[landscapeRotation] = - mStatusBarHeightForRotation[seascapeRotation] = - res.getDimensionPixelSize(R.dimen.status_bar_height_landscape); + getStatusBarHeightForRotation(landscapeRotation); + mStatusBarHeightForRotation[seascapeRotation] = + getStatusBarHeightForRotation(seascapeRotation); mDisplayCutoutTouchableRegionSize = res.getDimensionPixelSize( R.dimen.display_cutout_touchable_region_size); } else { @@ -2294,18 +2458,65 @@ public class DisplayPolicy { return mUiContext; } - private int getNavigationBarWidth(int rotation, int uiMode) { - if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) { - return mNavigationBarWidthForRotationInCarMode[rotation]; + private int getNavigationBarWidth(int rotation, int uiMode, int position) { + if (INSETS_LAYOUT_GENERALIZATION) { + if (mNavigationBar == null) { + return 0; + } + LayoutParams lp = mNavigationBar.mAttrs; + if (lp.paramsForRotation != null + && lp.paramsForRotation.length == 4 + && lp.paramsForRotation[rotation] != null) { + lp = lp.paramsForRotation[rotation]; + } + if (position == NAV_BAR_LEFT) { + if (lp.width > lp.providedInternalInsets.right) { + return lp.width - lp.providedInternalInsets.right; + } else { + return 0; + } + } else if (position == NAV_BAR_RIGHT) { + if (lp.width > lp.providedInternalInsets.left) { + return lp.width - lp.providedInternalInsets.left; + } else { + return 0; + } + } + return lp.width; } else { - return mNavigationBarWidthForRotationDefault[rotation]; + if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) { + return mNavigationBarWidthForRotationInCarMode[rotation]; + } else { + return mNavigationBarWidthForRotationDefault[rotation]; + } + } + } + + private int getAltBarWidth(@InternalInsetsType int insetsType) { + final InsetsSource source = mDisplayContent.getInsetsStateController().getRawInsetsState() + .peekSource(insetsType); + if (source == null) { + return 0; } + return source.getFrame().width(); + } + + private int getAltBarHeight(@InternalInsetsType int insetsType) { + final InsetsSource source = mDisplayContent.getInsetsStateController().getRawInsetsState() + .peekSource(insetsType); + if (source == null) { + return 0; + } + return source.getFrame().height(); } void notifyDisplayReady() { mHandler.post(() -> { final int displayId = getDisplayId(); - getStatusBarManagerInternal().onDisplayReady(displayId); + StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); + if (statusBar != null) { + statusBar.onDisplayReady(displayId); + } final WallpaperManagerInternal wpMgr = LocalServices .getService(WallpaperManagerInternal.class); if (wpMgr != null) { @@ -2314,31 +2525,22 @@ public class DisplayPolicy { }); } - /** - * Return the display width available after excluding any screen - * decorations that could never be removed in Honeycomb. That is, system bar or - * button bar. - */ - public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode, - DisplayCutout displayCutout) { - int width = fullWidth; - if (hasNavigationBar()) { - final int navBarPosition = navigationBarPosition(fullWidth, fullHeight, rotation); - if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) { - width -= getNavigationBarWidth(rotation, uiMode); - } - } - if (displayCutout != null) { - width -= displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight(); - } - return width; - } - private int getNavigationBarHeight(int rotation, int uiMode) { - if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) { - return mNavigationBarHeightForRotationInCarMode[rotation]; + if (INSETS_LAYOUT_GENERALIZATION) { + if (mNavigationBar == null) { + return 0; + } + LayoutParams lp = mNavigationBar.getLayoutingAttrs(rotation); + if (lp.height < lp.providedInternalInsets.top) { + return 0; + } + return lp.height - lp.providedInternalInsets.top; } else { - return mNavigationBarHeightForRotationDefault[rotation]; + if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) { + return mNavigationBarHeightForRotationInCarMode[rotation]; + } else { + return mNavigationBarHeightForRotationDefault[rotation]; + } } } @@ -2355,51 +2557,74 @@ public class DisplayPolicy { * @return navigation bar frame height */ private int getNavigationBarFrameHeight(int rotation, int uiMode) { - if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) { - return mNavigationBarHeightForRotationInCarMode[rotation]; + if (INSETS_LAYOUT_GENERALIZATION) { + if (mNavigationBar == null) { + return 0; + } + return mNavigationBar.mAttrs.height; } else { - return mNavigationBarFrameHeightForRotationDefault[rotation]; + if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) { + return mNavigationBarHeightForRotationInCarMode[rotation]; + } else { + return mNavigationBarFrameHeightForRotationDefault[rotation]; + } } } /** - * Return the display height available after excluding any screen + * Return the display size available after excluding any screen * decorations that could never be removed in Honeycomb. That is, system bar or * button bar. */ - public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode, + Point getNonDecorDisplaySize(int fullWidth, int fullHeight, int rotation, int uiMode, DisplayCutout displayCutout) { + int width = fullWidth; int height = fullHeight; + int navBarReducedHeight = 0; + int navBarReducedWidth = 0; + final int navBarPosition = navigationBarPosition(fullWidth, fullHeight, rotation); if (hasNavigationBar()) { - final int navBarPosition = navigationBarPosition(fullWidth, fullHeight, rotation); if (navBarPosition == NAV_BAR_BOTTOM) { - height -= getNavigationBarHeight(rotation, uiMode); + navBarReducedHeight = getNavigationBarHeight(rotation, uiMode); + } else if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) { + navBarReducedWidth = getNavigationBarWidth(rotation, uiMode, navBarPosition); + } + } + if (mExtraNavBarAlt != null) { + final LayoutParams altBarParams = mExtraNavBarAlt.getLayoutingAttrs(rotation); + final int altBarPosition = getAltBarPosition(altBarParams); + if (altBarPosition == ALT_BAR_BOTTOM || altBarPosition == ALT_BAR_TOP) { + if (altBarPosition == navBarPosition) { + navBarReducedHeight = Math.max(navBarReducedHeight, + getAltBarHeight(ITYPE_EXTRA_NAVIGATION_BAR)); + } else { + navBarReducedHeight += getAltBarHeight(ITYPE_EXTRA_NAVIGATION_BAR); + } + } else if (altBarPosition == ALT_BAR_LEFT || altBarPosition == ALT_BAR_RIGHT) { + if (altBarPosition == navBarPosition) { + navBarReducedWidth = Math.max(navBarReducedWidth, + getAltBarWidth(ITYPE_EXTRA_NAVIGATION_BAR)); + } else { + navBarReducedWidth += getAltBarWidth(ITYPE_EXTRA_NAVIGATION_BAR); + } } } + height -= navBarReducedHeight; + width -= navBarReducedWidth; if (displayCutout != null) { height -= displayCutout.getSafeInsetTop() + displayCutout.getSafeInsetBottom(); + width -= displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight(); } - return height; - } - - /** - * Return the available screen width that we should report for the - * configuration. This must be no larger than - * {@link #getNonDecorDisplayWidth(int, int, int, int, DisplayCutout)}; it may be smaller - * than that to account for more transient decoration like a status bar. - */ - public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode, - DisplayCutout displayCutout) { - return getNonDecorDisplayWidth(fullWidth, fullHeight, rotation, uiMode, displayCutout); + return new Point(width, height); } /** - * Return the available screen height that we should report for the + * Return the available screen size that we should report for the * configuration. This must be no larger than - * {@link #getNonDecorDisplayHeight(int, int, int, int, DisplayCutout)}; it may be smaller + * {@link #getNonDecorDisplaySize(int, int, int, int, DisplayCutout)}; it may be smaller * than that to account for more transient decoration like a status bar. */ - public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode, + Point getConfigDisplaySize(int fullWidth, int fullHeight, int rotation, int uiMode, DisplayCutout displayCutout) { // There is a separate status bar at the top of the display. We don't count that as part // of the fixed decor, since it can hide; however, for purposes of configurations, @@ -2411,8 +2636,9 @@ public class DisplayPolicy { // bar height. statusBarHeight = Math.max(0, statusBarHeight - displayCutout.getSafeInsetTop()); } - return getNonDecorDisplayHeight(fullWidth, fullHeight, rotation, uiMode, displayCutout) - - statusBarHeight; + final Point nonDecorSize = getNonDecorDisplaySize(fullWidth, fullHeight, rotation, + uiMode, displayCutout); + return new Point(nonDecorSize.x, nonDecorSize.y - statusBarHeight); } /** @@ -2423,7 +2649,7 @@ public class DisplayPolicy { */ float getWindowCornerRadius() { return mDisplayContent.getDisplay().getType() == TYPE_INTERNAL - ? ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources()) : 0f; + ? ScreenDecorationsUtils.getWindowCornerRadius(mContext) : 0f; } boolean isShowingDreamLw() { @@ -2460,7 +2686,7 @@ public class DisplayPolicy { /** * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system - * bar or button bar. See {@link #getNonDecorDisplayWidth}. + * bar or button bar. See {@link #getNonDecorDisplaySize}. * * @param displayRotation the current display rotation * @param displayWidth the current display width @@ -2472,16 +2698,34 @@ public class DisplayPolicy { DisplayCutout displayCutout, Rect outInsets) { outInsets.setEmpty(); - // Only navigation bar + // Only navigation bar and extra navigation bar if (hasNavigationBar()) { final int uiMode = mService.mPolicy.getUiMode(); int position = navigationBarPosition(displayWidth, displayHeight, displayRotation); if (position == NAV_BAR_BOTTOM) { outInsets.bottom = getNavigationBarHeight(displayRotation, uiMode); } else if (position == NAV_BAR_RIGHT) { - outInsets.right = getNavigationBarWidth(displayRotation, uiMode); + outInsets.right = getNavigationBarWidth(displayRotation, uiMode, position); } else if (position == NAV_BAR_LEFT) { - outInsets.left = getNavigationBarWidth(displayRotation, uiMode); + outInsets.left = getNavigationBarWidth(displayRotation, uiMode, position); + } + } + if (mExtraNavBarAlt != null) { + final LayoutParams extraNavLayoutParams = + mExtraNavBarAlt.getLayoutingAttrs(displayRotation); + final int position = getAltBarPosition(extraNavLayoutParams); + if (position == ALT_BAR_BOTTOM) { + outInsets.bottom = Math.max(outInsets.bottom, + getAltBarHeight(ITYPE_EXTRA_NAVIGATION_BAR)); + } else if (position == ALT_BAR_RIGHT) { + outInsets.right = Math.max(outInsets.right, + getAltBarWidth(ITYPE_EXTRA_NAVIGATION_BAR)); + } else if (position == ALT_BAR_LEFT) { + outInsets.left = Math.max(outInsets.left, + getAltBarWidth(ITYPE_EXTRA_NAVIGATION_BAR)); + } else if (position == ALT_BAR_TOP) { + outInsets.top = Math.max(outInsets.top, + getAltBarHeight(ITYPE_EXTRA_NAVIGATION_BAR)); } } @@ -2507,6 +2751,17 @@ public class DisplayPolicy { @NavigationBarPosition int navigationBarPosition(int displayWidth, int displayHeight, int displayRotation) { + if (INSETS_LAYOUT_GENERALIZATION && mNavigationBar != null) { + final int gravity = mNavigationBar.getLayoutingAttrs(displayRotation).gravity; + switch (gravity) { + case Gravity.LEFT: + return NAV_BAR_LEFT; + case Gravity.RIGHT: + return NAV_BAR_RIGHT; + default: + return NAV_BAR_BOTTOM; + } + } if (navigationBarCanMove() && displayWidth > displayHeight) { if (displayRotation == Surface.ROTATION_270) { return NAV_BAR_LEFT; @@ -2541,22 +2796,17 @@ public class DisplayPolicy { /** * A new window has been focused. */ - public int focusChangedLw(WindowState lastFocus, WindowState newFocus) { + public void focusChangedLw(WindowState lastFocus, WindowState newFocus) { mFocusedWindow = newFocus; mLastFocusedWindow = lastFocus; if (mDisplayContent.isDefaultDisplay) { mService.mPolicy.onDefaultDisplayFocusChangedLw(newFocus); } - if (updateSystemUiVisibilityLw()) { - // If the navigation bar has been hidden or shown, we need to do another - // layout pass to update that window. - return FINISH_LAYOUT_REDO_LAYOUT; - } - return 0; + updateSystemBarAttributes(); } - private void requestTransientBars(WindowState swipeTarget) { - if (!mService.mPolicy.isUserSetupComplete()) { + private void requestTransientBars(WindowState swipeTarget, boolean isGestureOnSystemBar) { + if (swipeTarget == null || !mService.mPolicy.isUserSetupComplete()) { // Swipe-up for navigation bar is disabled during setup return; } @@ -2592,7 +2842,8 @@ public class DisplayPolicy { if (controlTarget.canShowTransient()) { // Show transient bars if they are hidden; restore position if they are visible. - mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_SWIPE); + mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_SWIPE, + isGestureOnSystemBar); controlTarget.showInsets(restorePositionTypes, false); } else { // Restore visibilities and positions of system bars. @@ -2628,21 +2879,23 @@ public class DisplayPolicy { return mDisplayContent.getInsetsPolicy(); } - void resetSystemUiVisibilityLw() { + void resetSystemBarAttributes() { mLastDisableFlags = 0; - updateSystemUiVisibilityLw(); + updateSystemBarAttributes(); } - /** - * @return {@code true} if the update may affect the layout. - */ - boolean updateSystemUiVisibilityLw() { + void updateSystemBarAttributes() { + WindowState winCandidate = mFocusedWindow; + if (winCandidate == null && mTopFullscreenOpaqueWindowState != null + && (mTopFullscreenOpaqueWindowState.mAttrs.flags + & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0) { + // Only focusable window can take system bar control. + winCandidate = mTopFullscreenOpaqueWindowState; + } // If there is no window focused, there will be nobody to handle the events // anyway, so just hang on in whatever state we're in until things settle down. - WindowState winCandidate = mFocusedWindow != null ? mFocusedWindow - : mTopFullscreenOpaqueWindowState; if (winCandidate == null) { - return false; + return; } // The immersive mode confirmation should never affect the system bar visibility, otherwise @@ -2658,100 +2911,85 @@ public class DisplayPolicy { : lastFocusCanReceiveKeys ? mLastFocusedWindow : mTopFullscreenOpaqueWindowState; if (winCandidate == null) { - return false; + return; } } final WindowState win = winCandidate; mSystemUiControllingWindow = win; - mDisplayContent.getInsetsPolicy().updateBarControlTarget(win); - - final boolean inSplitScreen = - mService.mRoot.getDefaultTaskDisplayArea().isSplitScreenModeActivated(); - if (inSplitScreen) { - mService.getRootTaskBounds(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, - mDockedRootTaskBounds); - } else { - mDockedRootTaskBounds.setEmpty(); - } - mService.getRootTaskBounds(inSplitScreen ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY - : WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_UNDEFINED, mNonDockedRootTaskBounds); - final int fullscreenAppearance = getStatusBarAppearance(mTopFullscreenOpaqueWindowState, - mTopFullscreenOpaqueOrDimmingWindowState); - final int dockedAppearance = getStatusBarAppearance(mTopDockedOpaqueWindowState, - mTopDockedOpaqueOrDimmingWindowState); + final int displayId = getDisplayId(); final int disableFlags = win.getDisableFlags(); final int opaqueAppearance = updateSystemBarsLw(win, disableFlags); - final WindowState navColorWin = chooseNavigationColorWindowLw( - mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState, + final WindowState navColorWin = chooseNavigationColorWindowLw(mNavBarColorWindowCandidate, mDisplayContent.mInputMethodWindow, mNavigationBarPosition); final boolean isNavbarColorManagedByIme = navColorWin != null && navColorWin == mDisplayContent.mInputMethodWindow; - final int appearance = updateLightNavigationBarLw( - win.mAttrs.insetsFlags.appearance, mTopFullscreenOpaqueWindowState, - mTopFullscreenOpaqueOrDimmingWindowState, - mDisplayContent.mInputMethodWindow, navColorWin) | opaqueAppearance; + final int appearance = updateLightNavigationBarLw(win.mAttrs.insetsFlags.appearance, + navColorWin) | opaqueAppearance; final int behavior = win.mAttrs.insetsFlags.behavior; + final String focusedApp = win.mAttrs.packageName; final boolean isFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR) || !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR); - if (mLastDisableFlags == disableFlags - && mLastAppearance == appearance - && mLastFullscreenAppearance == fullscreenAppearance - && mLastDockedAppearance == dockedAppearance + final AppearanceRegion[] appearanceRegions = + new AppearanceRegion[mStatusBarColorWindows.size()]; + for (int i = mStatusBarColorWindows.size() - 1; i >= 0; i--) { + final WindowState windowState = mStatusBarColorWindows.get(i); + appearanceRegions[i] = new AppearanceRegion( + getStatusBarAppearance(windowState, windowState), + new Rect(windowState.getFrame())); + } + if (mLastDisableFlags != disableFlags) { + mLastDisableFlags = disableFlags; + final String cause = win.toString(); + callStatusBarSafely(statusBar -> statusBar.setDisableFlags(displayId, disableFlags, + cause)); + } + if (mLastAppearance == appearance && mLastBehavior == behavior + && mRequestedVisibilities.equals(win.getRequestedVisibilities()) + && Objects.equals(mFocusedApp, focusedApp) && mLastFocusIsFullscreen == isFullscreen - && mLastNonDockedRootTaskBounds.equals(mNonDockedRootTaskBounds) - && mLastDockedRootTaskBounds.equals(mDockedRootTaskBounds)) { - return false; + && Arrays.equals(mLastStatusBarAppearanceRegions, appearanceRegions)) { + return; } if (mDisplayContent.isDefaultDisplay && mLastFocusIsFullscreen != isFullscreen && ((mLastAppearance ^ appearance) & APPEARANCE_LOW_PROFILE_BARS) != 0) { mService.mInputManager.setSystemUiLightsOut( isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0); } - mLastDisableFlags = disableFlags; + final InsetsVisibilities requestedVisibilities = + new InsetsVisibilities(win.getRequestedVisibilities()); mLastAppearance = appearance; - mLastFullscreenAppearance = fullscreenAppearance; - mLastDockedAppearance = dockedAppearance; mLastBehavior = behavior; + mRequestedVisibilities = requestedVisibilities; + mFocusedApp = focusedApp; mLastFocusIsFullscreen = isFullscreen; - mLastNonDockedRootTaskBounds.set(mNonDockedRootTaskBounds); - mLastDockedRootTaskBounds.set(mDockedRootTaskBounds); - final Rect fullscreenRootTaskBounds = new Rect(mNonDockedRootTaskBounds); - final Rect dockedRootTaskBounds = new Rect(mDockedRootTaskBounds); - final AppearanceRegion[] appearanceRegions = inSplitScreen - ? new AppearanceRegion[]{ - new AppearanceRegion(fullscreenAppearance, fullscreenRootTaskBounds), - new AppearanceRegion(dockedAppearance, dockedRootTaskBounds)} - : new AppearanceRegion[]{ - new AppearanceRegion(fullscreenAppearance, fullscreenRootTaskBounds)}; - String cause = win.toString(); - mHandler.post(() -> { - StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); - if (statusBar != null) { - final int displayId = getDisplayId(); - statusBar.setDisableFlags(displayId, disableFlags, cause); - statusBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions, - isNavbarColorManagedByIme, behavior, isFullscreen); - - } - }); - return true; + mLastStatusBarAppearanceRegions = appearanceRegions; + callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId, + appearance, appearanceRegions, isNavbarColorManagedByIme, behavior, + requestedVisibilities, focusedApp)); } private int getStatusBarAppearance(WindowState opaque, WindowState opaqueOrDimming) { final boolean onKeyguard = isKeyguardShowing() && !isKeyguardOccluded(); final WindowState colorWin = onKeyguard ? mNotificationShade : opaqueOrDimming; - return isLightBarAllowed(colorWin, ITYPE_STATUS_BAR) && (colorWin == opaque || onKeyguard) + return isLightBarAllowed(colorWin, Type.statusBars()) && (colorWin == opaque || onKeyguard) ? (colorWin.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS) : 0; } + private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) { + mHandler.post(() -> { + StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); + if (statusBar != null) { + consumer.accept(statusBar); + } + }); + } + @VisibleForTesting @Nullable - static WindowState chooseNavigationColorWindowLw(WindowState opaque, - WindowState opaqueOrDimming, WindowState imeWindow, + static WindowState chooseNavigationColorWindowLw(WindowState candidate, WindowState imeWindow, @NavigationBarPosition int navBarPosition) { // If the IME window is visible and FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS is set, then IME // window can be navigation color window. @@ -2760,71 +2998,58 @@ public class DisplayPolicy { && navBarPosition == NAV_BAR_BOTTOM && (imeWindow.mAttrs.flags & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; - - if (opaque != null && opaqueOrDimming == opaque) { - // If the top fullscreen-or-dimming window is also the top fullscreen, respect it - // unless IME window is also eligible, since currently the IME window is always show - // above the opaque fullscreen app window, regardless of the IME target window. - // TODO(b/31559891): Maybe we need to revisit this condition once b/31559891 is fixed. - return imeWindowCanNavColorWindow ? imeWindow : opaque; - } - - if (opaqueOrDimming == null || !opaqueOrDimming.isDimming()) { - // No dimming window is involved. Determine the result only with the IME window. - return imeWindowCanNavColorWindow ? imeWindow : null; - } - if (!imeWindowCanNavColorWindow) { - // No IME window is involved. Determine the result only with opaqueOrDimming. - return opaqueOrDimming; + // No IME window is involved. Determine the result only with candidate window. + return candidate; } - // The IME window and the dimming window are competing. Check if the dimming window can be - // IME target or not. - if (LayoutParams.mayUseInputMethod(opaqueOrDimming.mAttrs.flags)) { - // The IME window is above the dimming window. - return imeWindow; - } else { - // The dimming window is above the IME window. - return opaqueOrDimming; + if (candidate != null && candidate.isDimming()) { + // The IME window and the dimming window are competing. Check if the dimming window can + // be IME target or not. + if (LayoutParams.mayUseInputMethod(candidate.mAttrs.flags)) { + // The IME window is above the dimming window. + return imeWindow; + } else { + // The dimming window is above the IME window. + return candidate; + } } + + return imeWindow; } @VisibleForTesting - int updateLightNavigationBarLw(int appearance, WindowState opaque, - WindowState opaqueOrDimming, WindowState imeWindow, WindowState navColorWin) { - - if (navColorWin != null) { - if (navColorWin == imeWindow || navColorWin == opaque) { - // Respect the light flag. - appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS; - appearance |= navColorWin.mAttrs.insetsFlags.appearance - & APPEARANCE_LIGHT_NAVIGATION_BARS; - } else if (navColorWin == opaqueOrDimming && navColorWin.isDimming()) { - // Clear the light flag for dimming window. - appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS; - } - } - if (!isLightBarAllowed(navColorWin, ITYPE_NAVIGATION_BAR)) { + int updateLightNavigationBarLw(int appearance, WindowState navColorWin) { + if (navColorWin == null || !isLightBarAllowed(navColorWin, Type.navigationBars())) { + // Clear the light flag while not allowed. appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS; + return appearance; } + + // Respect the light flag of navigation color window. + appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS; + appearance |= navColorWin.mAttrs.insetsFlags.appearance + & APPEARANCE_LIGHT_NAVIGATION_BARS; return appearance; } private int updateSystemBarsLw(WindowState win, int disableFlags) { - final boolean dockedRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea() - .isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - final boolean resizing = mDisplayContent.getDockedDividerController().isResizing(); - - // We need to force system bars when the docked root task is visible, when the freeform - // root task is focused but also when we are resizing for the transitions when docked - // root task visibility changes. - mForceShowSystemBars = dockedRootTaskVisible || win.inFreeformWindowingMode() || resizing; + final TaskDisplayArea defaultTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); + final boolean multiWindowTaskVisible = + defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) + || defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_MULTI_WINDOW); + final boolean freeformRootTaskVisible = + defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM); + + // We need to force showing system bars when the multi-window or freeform root task is + // visible. + mForceShowSystemBars = multiWindowTaskVisible || freeformRootTaskVisible; + mDisplayContent.getInsetsPolicy().updateBarControlTarget(win); int appearance = APPEARANCE_OPAQUE_NAVIGATION_BARS | APPEARANCE_OPAQUE_STATUS_BARS; - appearance = configureStatusBarOpacity(appearance); - appearance = configureNavBarOpacity(appearance, dockedRootTaskVisible, resizing); + appearance = configureNavBarOpacity(appearance, multiWindowTaskVisible, + freeformRootTaskVisible); final boolean requestHideNavBar = !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR); final long now = SystemClock.uptimeMillis(); @@ -2840,7 +3065,8 @@ public class DisplayPolicy { // we're no longer on the Keyguard and the screen is ready. We can now request the bars. mPendingPanicGestureUptime = 0; if (!isNavBarEmpty(disableFlags)) { - mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_PANIC); + mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_PANIC, + true /* isGestureOnSystemBar */); } } @@ -2860,17 +3086,33 @@ public class DisplayPolicy { return appearance; } - private boolean isLightBarAllowed(WindowState win, @InternalInsetsType int type) { + private static boolean isLightBarAllowed(WindowState win, @InsetsType int type) { if (win == null) { return false; } - final InsetsSource source = win.getInsetsState().peekSource(type); - return source != null && Rect.intersects(win.getFrame(), source.getFrame()); + return intersectsAnyInsets(win.getFrame(), win.getInsetsState(), type); } private Rect getBarContentFrameForWindow(WindowState win, int windowType) { final Rect rotatedBarFrame = win.mToken.getFixedRotationBarContentFrame(windowType); - return rotatedBarFrame != null ? rotatedBarFrame : mBarContentFrames.get(windowType); + if (rotatedBarFrame != null) { + return rotatedBarFrame; + } + if (!INSETS_LAYOUT_GENERALIZATION) { + return mBarContentFrames.get(windowType); + } + // We only need a window specific information for the fixed rotation, use raw insets state + // for all other cases. + InsetsState insetsState = mDisplayContent.getInsetsStateController().getRawInsetsState(); + final Rect tmpRect = new Rect(); + if (windowType == TYPE_NAVIGATION_BAR) { + tmpRect.set(insetsState.getSource(InsetsState.ITYPE_NAVIGATION_BAR).getFrame()); + } + if (windowType == TYPE_STATUS_BAR) { + tmpRect.set(insetsState.getSource(InsetsState.ITYPE_STATUS_BAR).getFrame()); + } + tmpRect.intersect(mDisplayContent.mDisplayFrames.mDisplayCutoutSafe); + return tmpRect; } /** @@ -2905,17 +3147,19 @@ public class DisplayPolicy { /** @return the current visibility flags with the status bar opacity related flags toggled. */ private int configureStatusBarOpacity(int appearance) { - final boolean fullscreenDrawsBackground = - drawsBarBackground(mTopFullscreenOpaqueWindowState); - final boolean dockedDrawsBackground = - drawsBarBackground(mTopDockedOpaqueWindowState); + boolean drawBackground = true; + boolean isFullyTransparentAllowed = true; + for (int i = mStatusBarBackgroundWindows.size() - 1; i >= 0; i--) { + final WindowState window = mStatusBarBackgroundWindows.get(i); + drawBackground &= drawsBarBackground(window); + isFullyTransparentAllowed &= isFullyTransparentAllowed(window, TYPE_STATUS_BAR); + } - if (fullscreenDrawsBackground && dockedDrawsBackground) { + if (drawBackground) { appearance &= ~APPEARANCE_OPAQUE_STATUS_BARS; } - if (!isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState, TYPE_STATUS_BAR) - || !isFullyTransparentAllowed(mTopDockedOpaqueWindowState, TYPE_STATUS_BAR)) { + if (!isFullyTransparentAllowed) { appearance |= APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS; } @@ -2926,53 +3170,35 @@ public class DisplayPolicy { * @return the current visibility flags with the nav-bar opacity related flags toggled based * on the nav bar opacity rules chosen by {@link #mNavBarOpacityMode}. */ - private int configureNavBarOpacity(int appearance, boolean dockedRootTaskVisible, - boolean isDockedDividerResizing) { - final boolean freeformRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea() - .isRootTaskVisible(WINDOWING_MODE_FREEFORM); - final boolean fullscreenDrawsBackground = - drawsBarBackground(mTopFullscreenOpaqueWindowState); - final boolean dockedDrawsBackground = - drawsBarBackground(mTopDockedOpaqueWindowState); + private int configureNavBarOpacity(int appearance, boolean multiWindowTaskVisible, + boolean freeformRootTaskVisible) { + final boolean drawBackground = drawsBarBackground(mNavBarBackgroundWindow); if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) { - if (fullscreenDrawsBackground && dockedDrawsBackground) { + if (drawBackground) { appearance = clearNavBarOpaqueFlag(appearance); - } else if (dockedRootTaskVisible) { - appearance = setNavBarOpaqueFlag(appearance); } } else if (mNavBarOpacityMode == NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED) { - if (dockedRootTaskVisible || freeformRootTaskVisible || isDockedDividerResizing) { + if (multiWindowTaskVisible || freeformRootTaskVisible) { if (mIsFreeformWindowOverlappingWithNavBar) { appearance = clearNavBarOpaqueFlag(appearance); - } else { - appearance = setNavBarOpaqueFlag(appearance); } - } else if (fullscreenDrawsBackground) { + } else if (drawBackground) { appearance = clearNavBarOpaqueFlag(appearance); } } else if (mNavBarOpacityMode == NAV_BAR_TRANSLUCENT_WHEN_FREEFORM_OPAQUE_OTHERWISE) { - if (isDockedDividerResizing) { - appearance = setNavBarOpaqueFlag(appearance); - } else if (freeformRootTaskVisible) { + if (freeformRootTaskVisible) { appearance = clearNavBarOpaqueFlag(appearance); - } else { - appearance = setNavBarOpaqueFlag(appearance); } } - if (!isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState, TYPE_NAVIGATION_BAR) - || !isFullyTransparentAllowed(mTopDockedOpaqueWindowState, TYPE_NAVIGATION_BAR)) { + if (!isFullyTransparentAllowed(mNavBarBackgroundWindow, TYPE_NAVIGATION_BAR)) { appearance |= APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS; } return appearance; } - private int setNavBarOpaqueFlag(int appearance) { - return appearance | APPEARANCE_OPAQUE_NAVIGATION_BARS; - } - private int clearNavBarOpaqueFlag(int appearance) { return appearance & ~APPEARANCE_OPAQUE_NAVIGATION_BARS; } @@ -3012,7 +3238,7 @@ public class DisplayPolicy { return; } mPendingPanicGestureUptime = SystemClock.uptimeMillis(); - updateSystemUiVisibilityLw(); + updateSystemBarAttributes(); } } }; @@ -3073,6 +3299,7 @@ public class DisplayPolicy { void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.println("DisplayPolicy"); prefix += " "; + final String prefixInner = prefix + " "; pw.print(prefix); pw.print("mCarDockEnablesAccelerometer="); pw.print(mCarDockEnablesAccelerometer); pw.print(" mDeskDockEnablesAccelerometer="); @@ -3140,14 +3367,27 @@ public class DisplayPolicy { pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState="); pw.println(mTopFullscreenOpaqueWindowState); } - if (mTopFullscreenOpaqueOrDimmingWindowState != null) { - pw.print(prefix); pw.print("mTopFullscreenOpaqueOrDimmingWindowState="); - pw.println(mTopFullscreenOpaqueOrDimmingWindowState); + if (mNavBarColorWindowCandidate != null) { + pw.print(prefix); pw.print("mNavBarColorWindowCandidate="); + pw.println(mNavBarColorWindowCandidate); } - if (mForcingShowNavBar) { - pw.print(prefix); pw.print("mForcingShowNavBar="); pw.println(mForcingShowNavBar); - pw.print(prefix); pw.print("mForcingShowNavBarLayer="); - pw.println(mForcingShowNavBarLayer); + if (mNavBarBackgroundWindow != null) { + pw.print(prefix); pw.print("mNavBarBackgroundWindow="); + pw.println(mNavBarBackgroundWindow); + } + if (!mStatusBarColorWindows.isEmpty()) { + pw.print(prefix); pw.println("mStatusBarColorWindows="); + for (int i = mStatusBarColorWindows.size() - 1; i >= 0; i--) { + final WindowState win = mStatusBarColorWindows.get(i); + pw.print(prefixInner); pw.println(win); + } + } + if (!mStatusBarBackgroundWindows.isEmpty()) { + pw.print(prefix); pw.println("mStatusBarBackgroundWindows="); + for (int i = mStatusBarBackgroundWindows.size() - 1; i >= 0; i--) { + final WindowState win = mStatusBarBackgroundWindows.get(i); + pw.print(prefixInner); pw.println(win); + } } pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen); pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar); @@ -3186,7 +3426,7 @@ public class DisplayPolicy { | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; lp.setFitInsetsTypes(0); - lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; if (ActivityManager.isHighEndGfx()) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; lp.privateFlags |= @@ -3226,17 +3466,39 @@ public class DisplayPolicy { } void release() { + mDisplayContent.mTransitionController.unregisterLegacyListener(mAppTransitionListener); mHandler.post(mGestureNavigationSettingsObserver::unregister); } @VisibleForTesting - static boolean isOverlappingWithNavBar(WindowState targetWindow, WindowState navBarWindow) { - if (navBarWindow == null || !navBarWindow.isVisible() - || targetWindow.mActivityRecord == null || !targetWindow.isVisible()) { + static boolean isOverlappingWithNavBar(@NonNull WindowState win) { + if (win.mActivityRecord == null || !win.isVisible()) { return false; } - return Rect.intersects(targetWindow.getFrame(), navBarWindow.getFrame()); + // When the window is dimming means it's requesting dim layer to its host container, so + // checking whether it's overlapping with a navigation bar by its container's bounds. + return intersectsAnyInsets(win.isDimming() ? win.getBounds() : win.getFrame(), + win.getInsetsState(), Type.navigationBars()); + } + + /** + * Returns whether the given {@param bounds} intersects with any insets of the + * provided {@param insetsType}. + */ + private static boolean intersectsAnyInsets(Rect bounds, InsetsState insetsState, + @InsetsType int insetsType) { + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(insetsType); + for (int i = 0; i < internalTypes.size(); i++) { + final InsetsSource source = insetsState.peekSource(internalTypes.valueAt(i)); + if (source == null || !source.isVisible()) { + continue; + } + if (Rect.intersects(bounds, source.getFrame())) { + return true; + } + } + return false; } /** diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 9f69fd933c16..3f130f5a1457 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -22,7 +22,6 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFA import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; -import static android.view.WindowManager.TRANSIT_CHANGE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN; @@ -484,7 +483,7 @@ public class DisplayRotation { */ boolean updateRotationUnchecked(boolean forceUpdate) { final boolean useShellTransitions = - mService.mAtmService.getTransitionController().getTransitionPlayer() != null; + mDisplayContent.mTransitionController.isShellTransitionsEnabled(); final int displayId = mDisplayContent.getDisplayId(); if (!forceUpdate && !useShellTransitions) { @@ -513,7 +512,9 @@ public class DisplayRotation { return false; } - if (mDisplayContent.mFixedRotationTransitionListener + final RecentsAnimationController recentsAnimController = + mService.getRecentsAnimationController(); + if (recentsAnimController != null && mDisplayContent.mFixedRotationTransitionListener .isTopFixedOrientationRecentsAnimating() // If screen is off or the device is going to sleep, then still allow to update. && mService.mPolicy.okToAnimate(false /* ignoreScreenOn */)) { @@ -521,6 +522,7 @@ public class DisplayRotation { // In order to ignore its requested orientation to avoid a sensor led rotation (e.g // user rotating the device while the recents animation is running), we ignore // rotation update while the animation is running. + recentsAnimController.setCheckRotationAfterCleanup(); return false; } } @@ -562,12 +564,6 @@ public class DisplayRotation { recentsAnimationController.cancelAnimationForDisplayChange(); } - final Transition t = (useShellTransitions - && !mService.mAtmService.getTransitionController().isCollecting()) - ? mService.mAtmService.getTransitionController().createTransition(TRANSIT_CHANGE) - : null; - mService.mAtmService.getTransitionController().collect(mDisplayContent); - ProtoLog.v(WM_DEBUG_ORIENTATION, "Display id=%d rotation changed to %d from %d, lastOrientation=%d", displayId, rotation, oldRotation, lastOrientation); @@ -581,11 +577,10 @@ public class DisplayRotation { mDisplayContent.setLayoutNeeded(); if (useShellTransitions) { - if (t != null) { - // This created its own transition, so send a start request. - mService.mAtmService.getTransitionController().requestStartTransition( - t, null /* trigger */, null /* remote */); - } else { + final boolean wasInTransition = mDisplayContent.inTransition(); + mDisplayContent.requestChangeTransitionIfNeeded( + ActivityInfo.CONFIG_WINDOW_CONFIGURATION); + if (wasInTransition) { // Use remote-rotation infra since the transition has already been requested // TODO(shell-transitions): Remove this once lifecycle management can cover all // rotation cases. @@ -661,17 +656,17 @@ public class DisplayRotation { mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout); mIsWaitingForRemoteRotation = false; - if (mService.mAtmService.getTransitionController().getTransitionPlayer() != null) { - if (!mService.mAtmService.getTransitionController().isCollecting()) { + if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) { + if (!mDisplayContent.mTransitionController.isCollecting()) { throw new IllegalStateException("Trying to rotate outside a transition"); } - mService.mAtmService.getTransitionController().collect(mDisplayContent); + mDisplayContent.mTransitionController.collect(mDisplayContent); // Go through all tasks and collect them before the rotation // TODO(shell-transitions): move collect() to onConfigurationChange once wallpaper // handling is synchronized. mDisplayContent.forAllTasks(task -> { if (task.isVisible()) { - mService.mAtmService.getTransitionController().collect(task); + mDisplayContent.mTransitionController.collect(task); } }); mDisplayContent.getInsetsStateController().addProvidersToTransition(); @@ -1408,7 +1403,7 @@ public class DisplayRotation { case ActivityInfo.SCREEN_ORIENTATION_USER: case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED: // Works with any rotation except upside down. - return (preferredRotation >= 0) && (preferredRotation != mUpsideDownRotation); + return (preferredRotation >= 0) && (preferredRotation != Surface.ROTATION_180); } return false; diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java index 8fcdf2e96889..4a70fa336838 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java +++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java @@ -264,8 +264,14 @@ class DisplayWindowSettingsProvider implements SettingsProvider { @NonNull private static AtomicFile getVendorSettingsFile() { - final File vendorFile = new File(Environment.getVendorDirectory(), + // First look under product path for treblized builds. + File vendorFile = new File(Environment.getProductDirectory(), VENDOR_DISPLAY_SETTINGS_FILE_PATH); + if (!vendorFile.exists()) { + // Try and look in vendor path. + vendorFile = new File(Environment.getVendorDirectory(), + VENDOR_DISPLAY_SETTINGS_FILE_PATH); + } return new AtomicFile(vendorFile, WM_DISPLAY_COMMIT_TAG); } diff --git a/services/core/java/com/android/server/wm/DockedTaskDividerController.java b/services/core/java/com/android/server/wm/DockedTaskDividerController.java index fb9d06441536..925a6d858a3d 100644 --- a/services/core/java/com/android/server/wm/DockedTaskDividerController.java +++ b/services/core/java/com/android/server/wm/DockedTaskDividerController.java @@ -46,7 +46,7 @@ public class DockedTaskDividerController { void setTouchRegion(Rect touchRegion) { mTouchRegion.set(touchRegion); // We need to report touchable region changes to accessibility. - if (mDisplayContent.mWmService.mAccessibilityController != null) { + if (mDisplayContent.mWmService.mAccessibilityController.hasCallbacks()) { mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMoved( mDisplayContent.getDisplayId()); } diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index d12d07ab7448..cc6a8807bb9d 100644 --- a/services/core/java/com/android/server/wm/DragDropController.java +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -22,6 +22,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.content.ClipData; +import android.content.Context; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -32,6 +33,7 @@ import android.view.Display; import android.view.IWindow; import android.view.SurfaceControl; import android.view.View; +import android.view.accessibility.AccessibilityManager; import com.android.server.wm.WindowManagerInternal.IDragDropCallback; @@ -43,7 +45,8 @@ import java.util.concurrent.atomic.AtomicReference; */ class DragDropController { private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f; - private static final long DRAG_TIMEOUT_MS = 5000; + static final long DRAG_TIMEOUT_MS = 5000; + private static final int A11Y_DRAG_TIMEOUT_DEFAULT_MS = 60000; // Messages for Handler. static final int MSG_DRAG_END_TIMEOUT = 0; @@ -151,36 +154,48 @@ class DragDropController { mDragState.mOriginalAlpha = alpha; mDragState.mToken = dragToken; mDragState.mDisplayContent = displayContent; + mDragState.mData = data; - final Display display = displayContent.getDisplay(); - if (!mCallback.get().registerInputChannel( - mDragState, display, mService.mInputManager, - callingWin.mInputChannel)) { - Slog.e(TAG_WM, "Unable to transfer touch focus"); - return null; - } + if ((flags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) == 0) { + final Display display = displayContent.getDisplay(); + if (!mCallback.get().registerInputChannel( + mDragState, display, mService.mInputManager, + callingWin.mInputChannel)) { + Slog.e(TAG_WM, "Unable to transfer touch focus"); + return null; + } - final SurfaceControl surfaceControl = mDragState.mSurfaceControl; - mDragState.mData = data; - mDragState.broadcastDragStartedLocked(touchX, touchY); - mDragState.overridePointerIconLocked(touchSource); - // remember the thumb offsets for later - mDragState.mThumbOffsetX = thumbCenterX; - mDragState.mThumbOffsetY = thumbCenterY; - - // Make the surface visible at the proper location - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag"); - - final SurfaceControl.Transaction transaction = mDragState.mTransaction; - transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha); - transaction.setPosition( - surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY); - transaction.show(surfaceControl); - displayContent.reparentToOverlay(transaction, surfaceControl); - callingWin.scheduleAnimation(); - - if (SHOW_LIGHT_TRANSACTIONS) { - Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag"); + final SurfaceControl surfaceControl = mDragState.mSurfaceControl; + mDragState.broadcastDragStartedLocked(touchX, touchY); + mDragState.overridePointerIconLocked(touchSource); + // remember the thumb offsets for later + mDragState.mThumbOffsetX = thumbCenterX; + mDragState.mThumbOffsetY = thumbCenterY; + + // Make the surface visible at the proper location + if (SHOW_LIGHT_TRANSACTIONS) { + Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag"); + } + + final SurfaceControl.Transaction transaction = mDragState.mTransaction; + transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha); + transaction.setPosition( + surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY); + transaction.show(surfaceControl); + displayContent.reparentToOverlay(transaction, surfaceControl); + callingWin.scheduleAnimation(); + if (SHOW_LIGHT_TRANSACTIONS) { + Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag"); + } + } else { + // Skip surface logic for a drag triggered by an AccessibilityAction + mDragState.broadcastDragStartedLocked(touchX, touchY); + + // Timeout for the user to drop the content + sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, callingWin.mClient.asBinder(), + getAccessibilityManager().getRecommendedTimeoutMillis( + A11Y_DRAG_TIMEOUT_DEFAULT_MS, + AccessibilityManager.FLAG_CONTENT_CONTROLS)); } } finally { if (surface != null) { @@ -309,10 +324,10 @@ class DragDropController { /** * Sends a timeout message to the Handler managed by DragDropController. */ - void sendTimeoutMessage(int what, Object arg) { + void sendTimeoutMessage(int what, Object arg, long timeoutMs) { mHandler.removeMessages(what, arg); final Message msg = mHandler.obtainMessage(what, arg); - mHandler.sendMessageDelayed(msg, DRAG_TIMEOUT_MS); + mHandler.sendMessageDelayed(msg, timeoutMs); } /** @@ -332,6 +347,30 @@ class DragDropController { } } + boolean dropForAccessibility(IWindow window, float x, float y) { + synchronized (mService.mGlobalLock) { + final boolean isA11yEnabled = getAccessibilityManager().isEnabled(); + if (!dragDropActiveLocked()) { + return false; + } + if (mDragState.isAccessibilityDragDrop() && isA11yEnabled) { + final WindowState winState = mService.windowForClientLocked( + null, window, false); + if (!mDragState.isWindowNotified(winState)) { + return false; + } + IBinder token = winState.mInputChannelToken; + return mDragState.reportDropWindowLock(token, x, y); + } + return false; + } + } + + AccessibilityManager getAccessibilityManager() { + return (AccessibilityManager) mService.mContext.getSystemService( + Context.ACCESSIBILITY_SERVICE); + } + private class DragHandler extends Handler { /** * Lock for window manager. diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index aa257f847e25..4fc123d3f68a 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -253,7 +253,7 @@ class DragState { mTransaction.reparent(mSurfaceControl, null).apply(); } else { mDragDropController.sendTimeoutMessage(MSG_REMOVE_DRAG_SURFACE_TIMEOUT, - mSurfaceControl); + mSurfaceControl, DragDropController.DRAG_TIMEOUT_MS); } mSurfaceControl = null; } @@ -276,9 +276,9 @@ class DragState { * Notify the drop target and tells it about the data. If the drop event is not sent to the * target, invokes {@code endDragLocked} immediately. */ - void reportDropWindowLock(IBinder token, float x, float y) { + boolean reportDropWindowLock(IBinder token, float x, float y) { if (mAnimator != null) { - return; + return false; } final WindowState touchedWin = mService.mInputToWindowMap.get(token); @@ -288,7 +288,7 @@ class DragState { mDragResult = false; endDragLocked(); if (DEBUG_DRAG) Slog.d(TAG_WM, "Drop outside a valid window " + touchedWin); - return; + return false; } if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin); @@ -322,16 +322,19 @@ class DragState { touchedWin.mClient.dispatchDragEvent(event); // 5 second timeout for this window to respond to the drop - mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, clientToken); + mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, clientToken, + DragDropController.DRAG_TIMEOUT_MS); } catch (RemoteException e) { Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin); endDragLocked(); + return false; } finally { if (myPid != touchedWin.mSession.mPid) { event.recycle(); } } mToken = clientToken; + return true; } class InputInterceptor { @@ -553,7 +556,7 @@ class DragState { } } - private boolean isWindowNotified(WindowState newWin) { + boolean isWindowNotified(WindowState newWin) { for (WindowState ws : mNotifiedWindows) { if (ws == newWin) { return true; @@ -567,8 +570,10 @@ class DragState { return; } if (!mDragResult) { - mAnimator = createReturnAnimationLocked(); - return; // Will call closeLocked() when the animation is done. + if (!isAccessibilityDragDrop()) { + mAnimator = createReturnAnimationLocked(); + return; // Will call closeLocked() when the animation is done. + } } closeLocked(); } @@ -577,7 +582,7 @@ class DragState { if (mAnimator != null) { return; } - if (!mDragInProgress || skipAnimation) { + if (!mDragInProgress || skipAnimation || isAccessibilityDragDrop()) { // mDragInProgress is false if an app invokes Session#cancelDragAndDrop before // Session#performDrag. Reset the drag state without playing the cancel animation // because H.DRAG_START_TIMEOUT may be sent to WindowManagerService, which will cause @@ -722,4 +727,8 @@ class DragState { mDragDropController.sendHandlerMessage(MSG_ANIMATION_END, null); } } + + boolean isAccessibilityDragDrop() { + return (mFlags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) != 0; + } } diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index b08d6e1dff9e..fc317a1212d5 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -127,7 +127,7 @@ class EmbeddedWindowController { } } - static class EmbeddedWindow { + static class EmbeddedWindow implements InputTarget { final IWindow mClient; @Nullable final WindowState mHostWindowState; @Nullable final ActivityRecord mHostActivityRecord; @@ -166,7 +166,8 @@ class EmbeddedWindowController { mDisplayId = displayId; } - String getName() { + @Override + public String toString() { final String hostWindowName = (mHostWindowState != null) ? mHostWindowState.getWindowTag().toString() : "Internal"; return "EmbeddedWindow{ u" + UserHandle.getUserId(mOwnerUid) + " " + hostWindowName @@ -183,7 +184,7 @@ class EmbeddedWindowController { } InputChannel openInputChannel() { - final String name = getName(); + final String name = toString(); mInputChannel = mWmService.mInputManager.createInputChannel(name); return mInputChannel; } @@ -195,5 +196,25 @@ class EmbeddedWindowController { mInputChannel = null; } } + + @Override + public WindowState getWindowState() { + return mHostWindowState; + } + + @Override + public int getDisplayId() { + return mDisplayId; + } + + @Override + public IWindow getIWindow() { + return mClient; + } + + @Override + public int getPid() { + return mOwnerPid; + } } } diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index 316c20ba5c47..badb1f5a0a12 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -16,30 +16,33 @@ package com.android.server.wm; +import static com.android.server.wm.ActivityRecord.State.INITIALIZING; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.Task.TAG_VISIBILITY; import android.annotation.Nullable; import android.util.Slog; +import java.util.ArrayList; + /** Helper class to ensure activities are in the right visible state for a container. */ class EnsureActivitiesVisibleHelper { - private final Task mTask; + private final TaskFragment mTaskFragment; private ActivityRecord mTop; private ActivityRecord mStarting; private boolean mAboveTop; private boolean mContainerShouldBeVisible; - private boolean mBehindFullscreenActivity; + private boolean mBehindFullyOccludedContainer; private int mConfigChanges; private boolean mPreserveWindows; private boolean mNotifyClients; - EnsureActivitiesVisibleHelper(Task container) { - mTask = container; + EnsureActivitiesVisibleHelper(TaskFragment container) { + mTaskFragment = container; } /** - * Update all attributes except {@link mTask} to use in subsequent calculations. + * Update all attributes except {@link mTaskFragment} to use in subsequent calculations. * * @param starting The activity that is being started * @param configChanges Parts of the configuration that changed for this activity for evaluating @@ -51,12 +54,12 @@ class EnsureActivitiesVisibleHelper { void reset(ActivityRecord starting, int configChanges, boolean preserveWindows, boolean notifyClients) { mStarting = starting; - mTop = mTask.topRunningActivity(); + mTop = mTaskFragment.topRunningActivity(); // If the top activity is not fullscreen, then we need to make sure any activities under it // are now visible. mAboveTop = mTop != null; - mContainerShouldBeVisible = mTask.shouldBeVisible(mStarting); - mBehindFullscreenActivity = !mContainerShouldBeVisible; + mContainerShouldBeVisible = mTaskFragment.shouldBeVisible(mStarting); + mBehindFullyOccludedContainer = !mContainerShouldBeVisible; mConfigChanges = configChanges; mPreserveWindows = preserveWindows; mNotifyClients = notifyClients; @@ -85,22 +88,59 @@ class EnsureActivitiesVisibleHelper { Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTop + " configChanges=0x" + Integer.toHexString(configChanges)); } - if (mTop != null) { - mTask.checkTranslucentActivityWaiting(mTop); + if (mTop != null && mTaskFragment.asTask() != null) { + // TODO(14709632): Check if this needed to be implemented in TaskFragment. + mTaskFragment.asTask().checkTranslucentActivityWaiting(mTop); } // We should not resume activities that being launched behind because these // activities are actually behind other fullscreen activities, but still required // to be visible (such as performing Recents animation). final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind - && mTask.isTopActivityFocusable() - && (starting == null || !starting.isDescendantOf(mTask)); - - mTask.forAllActivities(a -> { - setActivityVisibilityState(a, starting, resumeTopActivity); - }); - if (mTask.mAtmService.getTransitionController().getTransitionPlayer() != null) { - mTask.getDisplayContent().mWallpaperController.adjustWallpaperWindows(); + && mTaskFragment.isTopActivityFocusable() + && (starting == null || !starting.isDescendantOf(mTaskFragment)); + + ArrayList<TaskFragment> adjacentTaskFragments = null; + for (int i = mTaskFragment.mChildren.size() - 1; i >= 0; --i) { + final WindowContainer child = mTaskFragment.mChildren.get(i); + final TaskFragment childTaskFragment = child.asTaskFragment(); + if (childTaskFragment != null && childTaskFragment.topRunningActivity() != null) { + childTaskFragment.updateActivityVisibilities(starting, configChanges, + preserveWindows, notifyClients); + mBehindFullyOccludedContainer |= + childTaskFragment.getBounds().equals(mTaskFragment.getBounds()); + if (mAboveTop && mTop.getTaskFragment() == childTaskFragment) { + mAboveTop = false; + } + + if (mBehindFullyOccludedContainer) { + continue; + } + + if (adjacentTaskFragments != null && adjacentTaskFragments.contains( + childTaskFragment)) { + if (!childTaskFragment.isTranslucent(starting) + && !childTaskFragment.getAdjacentTaskFragment().isTranslucent( + starting)) { + // Everything behind two adjacent TaskFragments are occluded. + mBehindFullyOccludedContainer = true; + } + continue; + } + + final TaskFragment adjacentTaskFrag = childTaskFragment.getAdjacentTaskFragment(); + if (adjacentTaskFrag != null) { + if (adjacentTaskFragments == null) { + adjacentTaskFragments = new ArrayList<>(); + } + adjacentTaskFragments.add(adjacentTaskFrag); + } + } else if (child.asActivityRecord() != null) { + setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity); + } + } + if (mTaskFragment.mTransitionController.isShellTransitionsEnabled()) { + mTaskFragment.getDisplayContent().mWallpaperController.adjustWallpaperWindows(); } } @@ -112,7 +152,7 @@ class EnsureActivitiesVisibleHelper { } mAboveTop = false; - r.updateVisibilityIgnoringKeyguard(mBehindFullscreenActivity); + r.updateVisibilityIgnoringKeyguard(mBehindFullyOccludedContainer); final boolean reallyVisible = r.shouldBeVisibleUnchecked(); // Check whether activity should be visible without Keyguard influence @@ -122,12 +162,14 @@ class EnsureActivitiesVisibleHelper { if (DEBUG_VISIBILITY) { Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r + " containerVisible=" + mContainerShouldBeVisible - + " behindFullscreen=" + mBehindFullscreenActivity); + + " behindFullyOccluded=" + mBehindFullyOccludedContainer); } - mBehindFullscreenActivity = true; + mBehindFullyOccludedContainer = true; } else { - mBehindFullscreenActivity = false; + mBehindFullyOccludedContainer = false; } + } else if (r.isState(INITIALIZING)) { + r.cancelInitializing(); } if (reallyVisible) { @@ -173,24 +215,25 @@ class EnsureActivitiesVisibleHelper { Slog.v(TAG_VISIBILITY, "Make invisible? " + r + " finishing=" + r.finishing + " state=" + r.getState() + " containerShouldBeVisible=" + mContainerShouldBeVisible - + " behindFullscreenActivity=" + mBehindFullscreenActivity + + " behindFullyOccludedContainer=" + mBehindFullyOccludedContainer + " mLaunchTaskBehind=" + r.mLaunchTaskBehind); } r.makeInvisible(); } - if (!mBehindFullscreenActivity && mTask.isActivityTypeHome() && r.isRootOfTask()) { + if (!mBehindFullyOccludedContainer && mTaskFragment.isActivityTypeHome() + && r.isRootOfTask()) { if (DEBUG_VISIBILITY) { - Slog.v(TAG_VISIBILITY, "Home task: at " + mTask + Slog.v(TAG_VISIBILITY, "Home task: at " + mTaskFragment + " containerShouldBeVisible=" + mContainerShouldBeVisible - + " behindFullscreenActivity=" + mBehindFullscreenActivity); + + " behindOccludedParentContainer=" + mBehindFullyOccludedContainer); } // No other task in the root home task should be visible behind the home activity. // Home activities is usually a translucent activity with the wallpaper behind // them. However, when they don't have the wallpaper behind them, we want to // show activities in the next application root task behind them vs. another // task in the root home task like recents. - mBehindFullscreenActivity = true; + mBehindFullyOccludedContainer = true; } } @@ -219,7 +262,8 @@ class EnsureActivitiesVisibleHelper { r.setVisibility(true); } if (r != starting) { - mTask.mTaskSupervisor.startSpecificActivity(r, andResume, true /* checkConfig */); + mTaskFragment.mTaskSupervisor.startSpecificActivity(r, andResume, + true /* checkConfig */); } } } diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java index eab3f108d17a..52a7ac75e2dc 100644 --- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java +++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java @@ -42,6 +42,9 @@ public class FadeRotationAnimationController extends FadeAnimationController { /** A runnable which gets called when the {@link #show()} is called. */ private Runnable mOnShowRunnable; + /** Whether to use constant zero alpha animation. */ + private boolean mHideImmediately; + public FadeRotationAnimationController(DisplayContent displayContent) { super(displayContent); mService = displayContent.mWmService; @@ -51,6 +54,10 @@ public class FadeRotationAnimationController extends FadeAnimationController { mService.mWindowPlacerLocked.performSurfacePlacement(); } } : null; + if (mFrozenTimeoutRunnable != null) { + // Hide the windows immediately because screen should have been covered by screenshot. + mHideImmediately = true; + } final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); final WindowState navigationBar = displayPolicy.getNavigationBar(); if (navigationBar != null) { @@ -120,6 +127,15 @@ public class FadeRotationAnimationController extends FadeAnimationController { } } + /** Hides the window immediately until it is drawn in new rotation. */ + void hideImmediately(WindowToken windowToken) { + final boolean original = mHideImmediately; + mHideImmediately = true; + mTargetWindowTokens.add(windowToken); + fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM); + mHideImmediately = original; + } + /** Returns {@code true} if the window is handled by this controller. */ boolean isHandledToken(WindowToken token) { return token == mNavBarToken || isTargetToken(token); @@ -145,8 +161,7 @@ public class FadeRotationAnimationController extends FadeAnimationController { @Override public Animation getFadeOutAnimation() { - if (mFrozenTimeoutRunnable != null) { - // Hide the window immediately because screen should have been covered by screenshot. + if (mHideImmediately) { return new AlphaAnimation(0 /* fromAlpha */, 0 /* toAlpha */); } return super.getFadeOutAnimation(); diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index ed1e784bf275..cbefe7f3ade4 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -29,6 +29,7 @@ import static com.android.server.wm.ImeInsetsSourceProviderProto.IS_IME_LAYOUT_D import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Trace; import android.util.proto.ProtoOutputStream; import android.view.InsetsSource; @@ -91,6 +92,16 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { } @Override + void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) { + if (target != null && target.getWindow() != null) { + // ime control target could be a different window. + // Refer WindowState#getImeControlTarget(). + target = target.getWindow().getImeControlTarget(); + } + super.updateControlForTarget(target, force); + } + + @Override protected boolean updateClientVisibility(InsetsControlTarget caller) { boolean changed = super.updateClientVisibility(caller); if (changed && caller.getRequestedVisibility(mSource.getType())) { diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java index 747d3652e150..f3b9cdfd39e0 100644 --- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java +++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java @@ -20,6 +20,7 @@ import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.view.Display.DEFAULT_DISPLAY; import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; +import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; @@ -420,7 +421,7 @@ public class ImmersiveModeConfirmation { } final Bundle options = new Bundle(); - options.putInt(DisplayAreaPolicyBuilder.KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId); + options.putInt(KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId); return options; } diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index aa7e6c9c80fc..18a2c601f6d3 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -30,6 +30,7 @@ import android.util.Slog; import android.view.InputApplicationHandle; import android.view.KeyEvent; import android.view.WindowManager; +import android.view.WindowManagerPolicyConstants; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.input.InputManagerService; @@ -181,8 +182,8 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal @Override public int getPointerLayer() { return mService.mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_POINTER) - * WindowManagerService.TYPE_LAYER_MULTIPLIER - + WindowManagerService.TYPE_LAYER_OFFSET; + * WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER + + WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; } /** Callback to get pointer display id. */ diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index d417d56b6d31..6afd3355b0a1 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -48,7 +48,9 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.LOGTAG_INPUT_FOCUS; -import android.graphics.Rect; +import static java.lang.Integer.MAX_VALUE; + +import android.annotation.Nullable; import android.graphics.Region; import android.os.Handler; import android.os.IBinder; @@ -67,6 +69,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.Set; import java.util.function.Consumer; @@ -101,6 +104,15 @@ final class InputMonitor { private final ArrayMap<String, InputConsumerImpl> mInputConsumers = new ArrayMap(); /** + * Set when recents (overview) is active as part of a shell transition. While set, any focus + * going to the recents activity will be redirected to the Recents input consumer. Since we + * draw the live-tile above the recents activity, we also need to provide that activity as a + * z-layering reference so that we can place the recents input consumer above it. + */ + private WeakReference<ActivityRecord> mActiveRecentsActivity = null; + private WeakReference<ActivityRecord> mActiveRecentsLayerRef = null; + + /** * Representation of a input consumer that the policy has added to the window manager to consume * input events going to windows below it. */ @@ -279,6 +291,7 @@ final class InputMonitor { inputWindowHandle.setInputFeatures(w.mAttrs.inputFeatures); inputWindowHandle.setPaused(w.mActivityRecord != null && w.mActivityRecord.paused); inputWindowHandle.setVisible(w.isVisible()); + inputWindowHandle.setWindowToken(w.mClient); final boolean focusable = w.canReceiveKeys() && (mService.mPerDisplayFocusEnabled || mDisplayContent.isOnTop()); @@ -289,9 +302,6 @@ final class InputMonitor { && !mDisableWallpaperTouchEvents; inputWindowHandle.setHasWallpaper(hasWallpaper); - final Rect frame = w.getFrame(); - inputWindowHandle.setFrame(frame.left, frame.top, frame.right, frame.bottom); - // Surface insets are hardcoded to be the same in all directions // and we could probably deprecate the "left/right/top/bottom" concept. // we avoid reintroducing this concept by just choosing one of them here. @@ -301,33 +311,46 @@ final class InputMonitor { // what is on screen to what is actually being touched in the UI. inputWindowHandle.setScaleFactor(w.mGlobalScale != 1f ? (1f / w.mGlobalScale) : 1f); - final int flags = w.getSurfaceTouchableRegion(mTmpRegion, w.mAttrs.flags); - inputWindowHandle.setTouchableRegion(mTmpRegion); + // Update layout params flags to force the window to be not touch modal. We do this to + // restrict the window's touchable region to the task even if it request touches outside its + // window bounds. An example is a dialog in primary split should get touches outside its + // window within the primary task but should not get any touches going to the secondary + // task. + int flags = w.mAttrs.flags; + if (w.mAttrs.isModal()) { + flags = flags | FLAG_NOT_TOUCH_MODAL; + } inputWindowHandle.setLayoutParamsFlags(flags); - boolean useSurfaceCrop = false; + boolean useSurfaceBoundsAsTouchRegion = false; + SurfaceControl touchableRegionCrop = null; final Task task = w.getTask(); if (task != null) { - if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { + // TODO(b/165794636): Remove the special case for freeform window once drag resizing is + // handled by WM shell. + if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN + && !task.inFreeformWindowingMode()) { // If the window is in a TaskManaged by a TaskOrganizer then most cropping will // be applied using the SurfaceControl hierarchy from the Organizer. This means // we need to make sure that these changes in crop are reflected in the input // windows, and so ensure this flag is set so that the input crop always reflects // the surface hierarchy. - // TODO(b/168252846): we have some issues with modal-windows, so we need to cross - // that bridge now that we organize full-screen Tasks. - inputWindowHandle.setTouchableRegionCrop(null /* Use this surfaces crop */); - inputWindowHandle.setReplaceTouchableRegionWithCrop(true); - useSurfaceCrop = true; + useSurfaceBoundsAsTouchRegion = true; + + if (w.mAttrs.isModal()) { + TaskFragment parent = w.getTaskFragment(); + touchableRegionCrop = parent != null ? parent.getSurfaceControl() : null; + } } else if (task.cropWindowsToRootTaskBounds() && !w.inFreeformWindowingMode()) { - inputWindowHandle.setTouchableRegionCrop(task.getRootTask().getSurfaceControl()); - inputWindowHandle.setReplaceTouchableRegionWithCrop(false); - useSurfaceCrop = true; + touchableRegionCrop = task.getRootTask().getSurfaceControl(); } } - if (!useSurfaceCrop) { - inputWindowHandle.setReplaceTouchableRegionWithCrop(false); - inputWindowHandle.setTouchableRegionCrop(null); + inputWindowHandle.setReplaceTouchableRegionWithCrop(useSurfaceBoundsAsTouchRegion); + inputWindowHandle.setTouchableRegionCrop(touchableRegionCrop); + + if (!useSurfaceBoundsAsTouchRegion) { + w.getSurfaceTouchableRegion(mTmpRegion, w.mAttrs); + inputWindowHandle.setTouchableRegion(mTmpRegion); } } @@ -393,6 +416,21 @@ final class InputMonitor { } /** + * Inform InputMonitor when recents is active so it can enable the recents input consumer. + * @param activity The active recents activity. {@code null} means recents is not active. + * @param layer An activity whose Z-layer is used as a reference for how to sort the consumer. + */ + void setActiveRecents(@Nullable ActivityRecord activity, @Nullable ActivityRecord layer) { + final boolean clear = activity == null; + mActiveRecentsActivity = clear ? null : new WeakReference<>(activity); + mActiveRecentsLayerRef = clear ? null : new WeakReference<>(layer); + } + + private static <T> T getWeak(WeakReference<T> ref) { + return ref != null ? ref.get() : null; + } + + /** * Called when the current input focus changes. */ private void updateInputFocusRequest(InputConsumerImpl recentsAnimationInputConsumer) { @@ -402,8 +440,10 @@ final class InputMonitor { if (recentsAnimationInputConsumer != null && focus != null) { final RecentsAnimationController recentsAnimationController = mService.getRecentsAnimationController(); - final boolean shouldApplyRecentsInputConsumer = recentsAnimationController != null - && recentsAnimationController.shouldApplyInputConsumer(focus.mActivityRecord); + final boolean shouldApplyRecentsInputConsumer = (recentsAnimationController != null + && recentsAnimationController.shouldApplyInputConsumer(focus.mActivityRecord)) + // Shell transitions doesn't use RecentsAnimationController + || getWeak(mActiveRecentsActivity) != null; if (shouldApplyRecentsInputConsumer) { requestFocus(recentsAnimationInputConsumer.mWindowHandle.token, recentsAnimationInputConsumer.mName); @@ -503,6 +543,14 @@ final class InputMonitor { mInDrag = inDrag; resetInputConsumers(mInputTransaction); + // Update recents input consumer layer if active + if (mAddRecentsAnimationInputConsumerHandle + && getWeak(mActiveRecentsActivity) != null) { + final WindowContainer layer = getWeak(mActiveRecentsLayerRef); + mRecentsAnimationInputConsumer.show(mInputTransaction, + layer != null ? layer : getWeak(mActiveRecentsActivity)); + mAddRecentsAnimationInputConsumerHandle = false; + } mDisplayContent.forAllWindows(this, true /* traverseTopToBottom */); updateInputFocusRequest(mRecentsAnimationInputConsumer); @@ -537,11 +585,17 @@ final class InputMonitor { final int privateFlags = w.mAttrs.privateFlags; + // This only works for legacy transitions. if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) { if (recentsAnimationController.updateInputConsumerForApp( mRecentsAnimationInputConsumer.mWindowHandle)) { - mRecentsAnimationInputConsumer.show(mInputTransaction, w.mActivityRecord); - mAddRecentsAnimationInputConsumerHandle = false; + final DisplayArea targetDA = + recentsAnimationController.getTargetAppDisplayArea(); + if (targetDA != null) { + mRecentsAnimationInputConsumer.reparent(mInputTransaction, targetDA); + mRecentsAnimationInputConsumer.show(mInputTransaction, MAX_VALUE - 1); + mAddRecentsAnimationInputConsumerHandle = false; + } } } @@ -552,7 +606,7 @@ final class InputMonitor { rootTask.getSurfaceControl()); // We set the layer to z=MAX-1 so that it's always on top. mPipInputConsumer.reparent(mInputTransaction, rootTask); - mPipInputConsumer.show(mInputTransaction, Integer.MAX_VALUE - 1); + mPipInputConsumer.show(mInputTransaction, MAX_VALUE - 1); mAddPipInputConsumerHandle = false; } } diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java new file mode 100644 index 000000000000..c7d328a2b18a --- /dev/null +++ b/services/core/java/com/android/server/wm/InputTarget.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 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 android.view.IWindow; + +/** + * Common interface between focusable objects. + * + * Both WindowState and EmbeddedWindows can receive input. This consolidates some common properties + * of both targets. + */ +interface InputTarget { + /* Get the WindowState associated with the target. */ + WindowState getWindowState(); + + /* Display id of the target. */ + int getDisplayId(); + + /* Client IWindow for the target. */ + IWindow getIWindow(); + + /* Owning pid of the target. */ + int getPid(); +} + diff --git a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java index 7a4d13c2d697..0a24d3c69b17 100644 --- a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java +++ b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Region; import android.os.IBinder; +import android.view.IWindow; import android.view.InputApplicationHandle; import android.view.InputWindowHandle; import android.view.SurfaceControl; @@ -275,6 +276,14 @@ class InputWindowHandleWrapper { mChanged = true; } + void setWindowToken(IWindow windowToken) { + if (mHandle.getWindow() == windowToken) { + return; + } + mHandle.setWindowToken(windowToken); + mChanged = true; + } + @Override public String toString() { return mHandle + ", changed=" + mChanged; diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index a8e1c1cda72b..10ae152c3365 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -18,8 +18,6 @@ package com.android.server.wm; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.view.InsetsController.ANIMATION_TYPE_HIDE; import static android.view.InsetsController.ANIMATION_TYPE_SHOW; import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN; @@ -45,16 +43,20 @@ import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.InsetsState.InternalInsetsType; +import android.view.InternalInsetsAnimationController; import android.view.SurfaceControl; import android.view.SyncRtSurfaceTransactionApplier; +import android.view.WindowInsets.Type; import android.view.WindowInsetsAnimation; import android.view.WindowInsetsAnimation.Bounds; import android.view.WindowInsetsAnimationControlListener; +import android.view.WindowInsetsAnimationController; import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.server.DisplayThread; +import com.android.server.statusbar.StatusBarManagerInternal; /** * Policy that implements who gets control over the windows generating insets. @@ -135,15 +137,19 @@ class InsetsPolicy { abortTransient(); } mFocusedWin = focusedWin; - boolean forceShowsSystemBarsForWindowingMode = forceShowsSystemBarsForWindowingMode(); - InsetsControlTarget statusControlTarget = getStatusControlTarget(focusedWin, - forceShowsSystemBarsForWindowingMode); - InsetsControlTarget navControlTarget = getNavControlTarget(focusedWin, - forceShowsSystemBarsForWindowingMode); - mStateController.onBarControlTargetChanged(statusControlTarget, - getFakeControlTarget(focusedWin, statusControlTarget), + final InsetsControlTarget statusControlTarget = + getStatusControlTarget(focusedWin, false /* fake */); + final InsetsControlTarget navControlTarget = + getNavControlTarget(focusedWin, false /* fake */); + mStateController.onBarControlTargetChanged( + statusControlTarget, + statusControlTarget == mDummyControlTarget + ? getStatusControlTarget(focusedWin, true /* fake */) + : null, navControlTarget, - getFakeControlTarget(focusedWin, navControlTarget)); + navControlTarget == mDummyControlTarget + ? getNavControlTarget(focusedWin, true /* fake */) + : null); mStatusBar.updateVisibility(statusControlTarget, ITYPE_STATUS_BAR); mNavBar.updateVisibility(navControlTarget, ITYPE_NAVIGATION_BAR); } @@ -153,7 +159,7 @@ class InsetsPolicy { return provider != null && provider.hasWindow() && !provider.getSource().isVisible(); } - void showTransient(@InternalInsetsType int[] types) { + void showTransient(@InternalInsetsType int[] types, boolean isGestureOnSystemBar) { boolean changed = false; for (int i = types.length - 1; i >= 0; i--) { final @InternalInsetsType int type = types[i]; @@ -167,8 +173,12 @@ class InsetsPolicy { changed = true; } if (changed) { - mPolicy.getStatusBarManagerInternal().showTransient(mDisplayContent.getDisplayId(), - mShowingTransientTypes.toArray()); + StatusBarManagerInternal statusBarManagerInternal = + mPolicy.getStatusBarManagerInternal(); + if (statusBarManagerInternal != null) { + statusBarManagerInternal.showTransient(mDisplayContent.getDisplayId(), + mShowingTransientTypes.toArray(), isGestureOnSystemBar); + } updateBarControlTarget(mFocusedWin); // The leashes can be created while updating bar control target. The surface transaction @@ -303,9 +313,11 @@ class InsetsPolicy { abortTypes.add(type); } } - if (abortTypes.size() > 0) { - mPolicy.getStatusBarManagerInternal().abortTransient(mDisplayContent.getDisplayId(), - abortTypes.toArray()); + StatusBarManagerInternal statusBarManagerInternal = + mPolicy.getStatusBarManagerInternal(); + if (abortTypes.size() > 0 && statusBarManagerInternal != null) { + statusBarManagerInternal.abortTransient( + mDisplayContent.getDisplayId(), abortTypes.toArray()); } } } @@ -315,19 +327,17 @@ class InsetsPolicy { * updateBarControlTarget(mFocusedWin) after this invocation. */ private void abortTransient() { - mPolicy.getStatusBarManagerInternal().abortTransient(mDisplayContent.getDisplayId(), - mShowingTransientTypes.toArray()); + StatusBarManagerInternal statusBarManagerInternal = mPolicy.getStatusBarManagerInternal(); + if (statusBarManagerInternal != null) { + statusBarManagerInternal.abortTransient( + mDisplayContent.getDisplayId(), mShowingTransientTypes.toArray()); + } mShowingTransientTypes.clear(); } - private @Nullable InsetsControlTarget getFakeControlTarget(@Nullable WindowState focused, - InsetsControlTarget realControlTarget) { - return realControlTarget == mDummyControlTarget ? focused : null; - } - private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin, - boolean forceShowsSystemBarsForWindowingMode) { - if (mShowingTransientTypes.indexOf(ITYPE_STATUS_BAR) != -1) { + boolean fake) { + if (!fake && isShowingTransientTypes(Type.statusBars())) { return mDummyControlTarget; } final WindowState notificationShade = mPolicy.getNotificationShade(); @@ -340,13 +350,12 @@ class InsetsPolicy { focusedWin.mAttrs.packageName); return mDisplayContent.mRemoteInsetsControlTarget; } - if (forceShowsSystemBarsForWindowingMode) { - // Status bar is forcibly shown for the windowing mode which is a steady state. - // We don't want the client to control the status bar, and we will dispatch the real - // visibility of status bar to the client. + if (mPolicy.areSystemBarsForcedShownLw()) { + // Status bar is forcibly shown. We don't want the client to control the status bar, and + // we will dispatch the real visibility of status bar to the client. return null; } - if (forceShowsStatusBarTransiently()) { + if (forceShowsStatusBarTransiently() && !fake) { // Status bar is forcibly shown transiently, and its new visibility won't be // dispatched to the client so that we can keep the layout stable. We will dispatch the // fake control to the client, so that it can re-show the bar during this scenario. @@ -372,13 +381,13 @@ class InsetsPolicy { } private @Nullable InsetsControlTarget getNavControlTarget(@Nullable WindowState focusedWin, - boolean forceShowsSystemBarsForWindowingMode) { + boolean fake) { final WindowState imeWin = mDisplayContent.mInputMethodWindow; if (imeWin != null && imeWin.isVisible()) { // Force showing navigation bar while IME is visible. return null; } - if (mShowingTransientTypes.indexOf(ITYPE_NAVIGATION_BAR) != -1) { + if (!fake && isShowingTransientTypes(Type.navigationBars())) { return mDummyControlTarget; } if (focusedWin == mPolicy.getNotificationShade()) { @@ -390,13 +399,12 @@ class InsetsPolicy { focusedWin.mAttrs.packageName); return mDisplayContent.mRemoteInsetsControlTarget; } - if (forceShowsSystemBarsForWindowingMode) { - // Navigation bar is forcibly shown for the windowing mode which is a steady state. - // We don't want the client to control the navigation bar, and we will dispatch the real - // visibility of navigation bar to the client. + if (mPolicy.areSystemBarsForcedShownLw()) { + // Navigation bar is forcibly shown. We don't want the client to control the navigation + // bar, and we will dispatch the real visibility of navigation bar to the client. return null; } - if (forceShowsNavigationBarTransiently()) { + if (forceShowsNavigationBarTransiently() && !fake) { // Navigation bar is forcibly shown transiently, and its new visibility won't be // dispatched to the client so that we can keep the layout stable. We will dispatch the // fake control to the client, so that it can re-show the bar during this scenario. @@ -405,6 +413,16 @@ class InsetsPolicy { return focusedWin; } + private boolean isShowingTransientTypes(@Type.InsetsType int types) { + final IntArray showingTransientTypes = mShowingTransientTypes; + for (int i = showingTransientTypes.size() - 1; i >= 0; i--) { + if ((InsetsState.toPublicType(showingTransientTypes.get(i)) & types) != 0) { + return true; + } + } + return false; + } + /** * Determines whether the remote insets controller should take control of system bars for all * windows. @@ -438,19 +456,6 @@ class InsetsPolicy { && (win.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0; } - private boolean forceShowsSystemBarsForWindowingMode() { - final boolean isDockedRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea() - .isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - final boolean isFreeformRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea() - .isRootTaskVisible(WINDOWING_MODE_FREEFORM); - final boolean isResizing = mDisplayContent.getDockedDividerController().isResizing(); - - // We need to force system bars when the docked root task is visible, when the freeform - // root task is visible but also when we are resizing for the transitions when docked - // root task visibility changes. - return isDockedRootTaskVisible || isFreeformRootTaskVisible || isResizing; - } - @VisibleForTesting void startAnimation(boolean show, Runnable callback) { int typesReady = 0; @@ -495,8 +500,12 @@ class InsetsPolicy { final int state = visible ? WINDOW_STATE_SHOWING : WINDOW_STATE_HIDDEN; if (mState != state) { mState = state; - mPolicy.getStatusBarManagerInternal().setWindowState( - mDisplayContent.getDisplayId(), mId, state); + StatusBarManagerInternal statusBarManagerInternal = + mPolicy.getStatusBarManagerInternal(); + if (statusBarManagerInternal != null) { + statusBarManagerInternal.setWindowState( + mDisplayContent.getDisplayId(), mId, state); + } } } } @@ -588,8 +597,8 @@ class InsetsPolicy { } @Override - public void startAnimation(InsetsAnimationControlImpl controller, - WindowInsetsAnimationControlListener listener, int types, + public <T extends InsetsAnimationControlRunner & InternalInsetsAnimationController> + void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types, WindowInsetsAnimation animation, Bounds bounds) { } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 7daebff2ccc2..3948eeec20b0 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -22,7 +22,7 @@ import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; -import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS; import static com.android.server.wm.InsetsSourceProviderProto.CAPTURED_LEASH; import static com.android.server.wm.InsetsSourceProviderProto.CLIENT_VISIBLE; import static com.android.server.wm.InsetsSourceProviderProto.CONTROL; @@ -108,6 +108,16 @@ class InsetsSourceProvider { private final boolean mControllable; + /** + * Whether to forced the dimensions of the source window to the inset frame and crop out any + * overflow. + * Used to crop the taskbar inset source when a task animation is occurring to hide the taskbar + * rounded corners overlays. + * + * TODO: Remove when we enable shell transitions (b/202383002) + */ + private boolean mCropToProvidingInsets = false; + InsetsSourceProvider(InsetsSource source, InsetsStateController stateController, DisplayContent displayContent) { mClientVisible = InsetsState.getDefaultVisibility(source.getType()); @@ -163,8 +173,10 @@ class InsetsSourceProvider { // animate-out as new one animates-in. mWin.cancelAnimation(); mWin.mProvidedInsetsSources.remove(mSource.getType()); + mSeamlessRotating = false; } - ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win); + ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s", win, + InsetsState.typeToString(mSource.getType())); mWin = win; mFrameProvider = frameProvider; mImeFrameProvider = imeFrameProvider; @@ -266,7 +278,7 @@ class InsetsSourceProvider { && mWin.okToDisplay()) { mWin.applyWithNextDraw(mSetLeashPositionConsumer); } else { - mSetLeashPositionConsumer.accept(mWin.getPendingTransaction()); + mSetLeashPositionConsumer.accept(mWin.getSyncTransaction()); } } if (mServerVisible && !mLastSourceFrame.equals(mSource.getFrame())) { @@ -301,17 +313,68 @@ class InsetsSourceProvider { mFakeControlTarget = fakeTarget; } + /** + * Ensures that the inset source window is cropped so that anything that doesn't fit within the + * inset frame is cropped out until removeCropToProvidingInsetsBounds is called. + * + * The inset source surface will get cropped to the be of the size of the insets it's providing. + * + * For example, for the taskbar window which serves as the ITYPE_EXTRA_NAVIGATION_BAR inset + * source, the window is larger than the insets because of the rounded corners overlay, but + * during task animations we want to make sure that the overlay is cropped out of the window so + * that they don't hide the window animations. + * + * @param t The transaction to use to apply immediate overflow cropping operations. + * + * NOTE: The relies on the inset source window to have a leash (usually this would be a leash + * for the ANIMATION_TYPE_INSETS_CONTROL animation if the inset is controlled by the client) + * + * TODO: Remove when we migrate over to shell transitions (b/202383002) + */ + void setCropToProvidingInsetsBounds(Transaction t) { + mCropToProvidingInsets = true; + + if (mWin != null && mWin.mSurfaceAnimator.hasLeash()) { + // apply to existing leash + t.setWindowCrop(mWin.mSurfaceAnimator.mLeash, getProvidingInsetsBoundsCropRect()); + } + } + + /** + * Removes any overflow cropping and future cropping to the inset source window's leash that may + * have been set with a call to setCropToProvidingInsetsBounds(). + * @param t The transaction to use to apply immediate removal of overflow cropping. + * + * TODO: Remove when we migrate over to shell transitions (b/202383002) + */ + void removeCropToProvidingInsetsBounds(Transaction t) { + mCropToProvidingInsets = false; + + // apply to existing leash + if (mWin != null && mWin.mSurfaceAnimator.hasLeash()) { + t.setWindowCrop(mWin.mSurfaceAnimator.mLeash, null); + } + } + + private Rect getProvidingInsetsBoundsCropRect() { + Rect sourceWindowFrame = mWin.getFrame(); + Rect insetFrame = getSource().getFrame(); + + // The rectangle in buffer space we want to crop to + return new Rect( + insetFrame.left - sourceWindowFrame.left, + insetFrame.top - sourceWindowFrame.top, + insetFrame.right - sourceWindowFrame.left, + insetFrame.bottom - sourceWindowFrame.top + ); + } + void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) { if (mSeamlessRotating) { // We are un-rotating the window against the display rotation. We don't want the target // to control the window for now. return; } - if (target != null && target.getWindow() != null) { - // ime control target could be a different window. - // Refer WindowState#getImeControlTarget(). - target = target.getWindow().getImeControlTarget(); - } if (mWin != null && mWin.getSurfaceControl() == null) { // if window doesn't have a surface, set it null and return. @@ -335,7 +398,7 @@ class InsetsSourceProvider { if (getSource().getType() == ITYPE_IME) { setClientVisible(target.getRequestedVisibility(mSource.getType())); } - final Transaction t = mDisplayContent.getPendingTransaction(); + final Transaction t = mDisplayContent.getSyncTransaction(); mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */, ANIMATION_TYPE_INSETS_CONTROL); @@ -348,7 +411,7 @@ class InsetsSourceProvider { updateVisibility(); mControl = new InsetsSourceControl(mSource.getType(), leash, surfacePosition, mSource.calculateInsets(mWin.getBounds(), true /* ignoreVisibility */)); - ProtoLog.d(WM_DEBUG_IME, + ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource Control %s for target %s", mControl, mControlTarget); } @@ -381,8 +444,11 @@ class InsetsSourceProvider { return; } mClientVisible = clientVisible; - mDisplayContent.mWmService.mH.obtainMessage( - LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED, mDisplayContent).sendToTarget(); + if (!mDisplayContent.mLayoutAndAssignWindowLayersScheduled) { + mDisplayContent.mLayoutAndAssignWindowLayersScheduled = true; + mDisplayContent.mWmService.mH.obtainMessage( + LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED, mDisplayContent).sendToTarget(); + } updateVisibility(); } @@ -394,8 +460,9 @@ class InsetsSourceProvider { protected void updateVisibility() { mSource.setVisible(mServerVisible && (isMirroredSource() || mClientVisible)); - ProtoLog.d(WM_DEBUG_IME, - "InsetsSource updateVisibility serverVisible: %s clientVisible: %s", + ProtoLog.d(WM_DEBUG_WINDOW_INSETS, + "InsetsSource updateVisibility for %s, serverVisible: %s clientVisible: %s", + InsetsState.typeToString(mSource.getType()), mServerVisible, mClientVisible); } @@ -534,19 +601,24 @@ class InsetsSourceProvider { @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, - @AnimationType int type, OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) { // TODO(b/166736352): Check if we still need to control the IME visibility here. if (mSource.getType() == ITYPE_IME) { // TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed. t.setAlpha(animationLeash, 1 /* alpha */); t.hide(animationLeash); } - ProtoLog.i(WM_DEBUG_IME, + ProtoLog.i(WM_DEBUG_WINDOW_INSETS, "ControlAdapter startAnimation mSource: %s controlTarget: %s", mSource, mControlTarget); mCapturedLeash = animationLeash; t.setPosition(mCapturedLeash, mSurfacePosition.x, mSurfacePosition.y); + + if (mCropToProvidingInsets) { + // Apply crop to hide overflow + t.setWindowCrop(mCapturedLeash, getProvidingInsetsBoundsCropRect()); + } } @Override @@ -557,7 +629,7 @@ class InsetsSourceProvider { mControlTarget = null; mAdapter = null; setClientVisible(InsetsState.getDefaultVisibility(mSource.getType())); - ProtoLog.i(WM_DEBUG_IME, + ProtoLog.i(WM_DEBUG_WINDOW_INSETS, "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s", mSource, mControlTarget); } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 655007cf3cd1..c4ca8e364011 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -254,7 +254,7 @@ class InsetsStateController { if (p == null) continue; final WindowContainer wc = p.mWin; if (wc == null) continue; - mDisplayContent.mAtmService.getTransitionController().collect(wc); + mDisplayContent.mTransitionController.collect(wc); } } @@ -385,7 +385,7 @@ class InsetsStateController { if (changed) { notifyInsetsChanged(); mDisplayContent.updateSystemGestureExclusion(); - mDisplayContent.getDisplayPolicy().updateSystemUiVisibilityLw(); + mDisplayContent.getDisplayPolicy().updateSystemBarAttributes(); } } diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 054854aef121..bd41de3a9509 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; @@ -27,6 +28,7 @@ import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_W import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; +import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; @@ -49,6 +51,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.Display; +import android.view.WindowManager; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.server.inputmethod.InputMethodManagerInternal; @@ -158,6 +161,7 @@ class KeyguardController { final boolean keyguardChanged = (keyguardShowing != mKeyguardShowing) || (mKeyguardGoingAway && keyguardShowing && !aodChanged); if (!keyguardChanged && !aodChanged) { + setWakeTransitionReady(); return; } EventLogTags.writeWmSetKeyguardShown( @@ -202,6 +206,15 @@ class KeyguardController { updateKeyguardSleepToken(); mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); InputMethodManagerInternal.get().updateImeWindowStatus(false /* disableImeIcon */); + setWakeTransitionReady(); + } + + private void setWakeTransitionReady() { + if (mWindowManager.mAtmService.getTransitionController().getCollectingTransitionType() + == WindowManager.TRANSIT_WAKE) { + mWindowManager.mAtmService.getTransitionController().setReady( + mRootWindowContainer.getDefaultDisplay()); + } } /** @@ -223,8 +236,14 @@ class KeyguardController { mAodShowing ? 1 : 0, 1 /* keyguardGoingAway */, "keyguardGoingAway"); - mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare( - TRANSIT_KEYGUARD_GOING_AWAY, convertTransitFlags(flags)); + final int transitFlags = convertTransitFlags(flags); + final DisplayContent dc = mRootWindowContainer.getDefaultDisplay(); + dc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, transitFlags); + // We are deprecating TRANSIT_KEYGUARD_GOING_AWAY for Shell transition and use + // TRANSIT_FLAG_KEYGUARD_GOING_AWAY to indicate that it should animate keyguard going + // away. + dc.mAtmService.getTransitionController().requestTransitionIfNeeded( + TRANSIT_TO_BACK, transitFlags, null /* trigger */, dc); updateKeyguardSleepToken(); // Some stack visibility might change (e.g. docked stack) @@ -264,7 +283,7 @@ class KeyguardController { } private int convertTransitFlags(int keyguardGoingAwayFlags) { - int result = 0; + int result = TRANSIT_FLAG_KEYGUARD_GOING_AWAY; if ((keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_TO_SHADE) != 0) { result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; } @@ -333,6 +352,7 @@ class KeyguardController { for (int displayNdx = mRootWindowContainer.getChildCount() - 1; displayNdx >= 0; displayNdx--) { final DisplayContent display = mRootWindowContainer.getChildAt(displayNdx); + if (display.isRemoving() || display.isRemoved()) continue; final KeyguardDisplayState state = getDisplayState(display.mDisplayId); state.updateVisibility(this, display); requestDismissKeyguard |= state.mRequestDismissKeyguard; @@ -354,7 +374,7 @@ class KeyguardController { // TODO(b/113840485): Handle app transition for individual display, and apply occluded // state change to secondary displays. // For now, only default display fully supports occluded change. Other displays only - // updates keygaurd sleep token on that display. + // updates keyguard sleep token on that display. if (displayId != DEFAULT_DISPLAY) { updateKeyguardSleepToken(displayId); return; @@ -365,19 +385,10 @@ class KeyguardController { mService.deferWindowLayout(); try { mRootWindowContainer.getDefaultDisplay() - .prepareAppTransition( + .requestTransitionAndLegacyPrepare( isDisplayOccluded(DEFAULT_DISPLAY) ? TRANSIT_KEYGUARD_OCCLUDE - : TRANSIT_KEYGUARD_UNOCCLUDE); - // When the occluding activity also turns on the display, visibility of the activity - // can be committed before KEYGUARD_OCCLUDE transition is handled. - // Set mRequestForceTransition flag to make sure that the app transition animation - // is applied for such case. - // TODO(b/194243906): Fix this before enabling the remote keyguard animation. - if (WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation - && topActivity != null) { - topActivity.mRequestForceTransition = true; - } + : TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */); updateKeyguardSleepToken(DEFAULT_DISPLAY); mWindowManager.executeAppTransition(); } finally { diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java index 3dbe79df6722..4b98013a99cc 100644 --- a/services/core/java/com/android/server/wm/Letterbox.java +++ b/services/core/java/com/android/server/wm/Letterbox.java @@ -19,19 +19,24 @@ package com.android.server.wm; import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static android.view.SurfaceControl.HIDDEN; +import android.content.Context; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.os.Process; +import android.view.GestureDetector; import android.view.InputChannel; +import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.InputWindowHandle; +import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.WindowManager; import com.android.server.UiThread; +import java.util.function.IntConsumer; import java.util.function.Supplier; /** @@ -58,11 +63,15 @@ public class Letterbox { private final LetterboxSurface mLeft = new LetterboxSurface("left"); private final LetterboxSurface mBottom = new LetterboxSurface("bottom"); private final LetterboxSurface mRight = new LetterboxSurface("right"); - // Prevents wallpaper from peeking through near rounded corners. It's not included in - // mSurfaces array since it isn't needed in methods like notIntersectsOrFullyContains - // or attachInput. - private final LetterboxSurface mBehind = new LetterboxSurface("behind"); + // One surface that fills the whole window is used over multiple surfaces to: + // - Prevents wallpaper from peeking through near rounded corners. + // - For "blurred wallpaper" background, to avoid having visible border between surfaces. + // One surface approach isn't always preferred over multiple surfaces due to rendering cost + // for overlaping an app window and letterbox surfaces. + private final LetterboxSurface mFullWindowSurface = new LetterboxSurface("fullWindow"); private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom }; + // Reachability gestures. + private final IntConsumer mDoubleTapCallback; /** * Constructs a Letterbox. @@ -75,7 +84,8 @@ public class Letterbox { Supplier<Color> colorSupplier, Supplier<Boolean> hasWallpaperBackgroundSupplier, Supplier<Integer> blurRadiusSupplier, - Supplier<Float> darkScrimAlphaSupplier) { + Supplier<Float> darkScrimAlphaSupplier, + IntConsumer doubleTapCallback) { mSurfaceControlFactory = surfaceControlFactory; mTransactionFactory = transactionFactory; mAreCornersRounded = areCornersRounded; @@ -83,6 +93,7 @@ public class Letterbox { mHasWallpaperBackgroundSupplier = hasWallpaperBackgroundSupplier; mBlurRadiusSupplier = blurRadiusSupplier; mDarkScrimAlphaSupplier = darkScrimAlphaSupplier; + mDoubleTapCallback = doubleTapCallback; } /** @@ -104,7 +115,7 @@ public class Letterbox { mLeft.layout(outer.left, outer.top, inner.left, outer.bottom, surfaceOrigin); mBottom.layout(outer.left, inner.bottom, outer.right, outer.bottom, surfaceOrigin); mRight.layout(inner.right, outer.top, outer.right, outer.bottom, surfaceOrigin); - mBehind.layout(inner.left, inner.top, inner.right, inner.bottom, surfaceOrigin); + mFullWindowSurface.layout(outer.left, outer.top, outer.right, outer.bottom, surfaceOrigin); } @@ -168,37 +179,46 @@ public class Letterbox { for (LetterboxSurface surface : mSurfaces) { surface.remove(); } - mBehind.remove(); + mFullWindowSurface.remove(); } /** Returns whether a call to {@link #applySurfaceChanges} would change the surface. */ public boolean needsApplySurfaceChanges() { + if (useFullWindowSurface()) { + return mFullWindowSurface.needsApplySurfaceChanges(); + } for (LetterboxSurface surface : mSurfaces) { if (surface.needsApplySurfaceChanges()) { return true; } } - if (mAreCornersRounded.get() && mBehind.needsApplySurfaceChanges()) { - return true; - } return false; } public void applySurfaceChanges(SurfaceControl.Transaction t) { - for (LetterboxSurface surface : mSurfaces) { - surface.applySurfaceChanges(t); - } - if (mAreCornersRounded.get()) { - mBehind.applySurfaceChanges(t); + if (useFullWindowSurface()) { + mFullWindowSurface.applySurfaceChanges(t); + + for (LetterboxSurface surface : mSurfaces) { + surface.remove(); + } } else { - mBehind.remove(); + for (LetterboxSurface surface : mSurfaces) { + surface.applySurfaceChanges(t); + } + + mFullWindowSurface.remove(); } } /** Enables touches to slide into other neighboring surfaces. */ void attachInput(WindowState win) { - for (LetterboxSurface surface : mSurfaces) { - surface.attachInput(win); + if (useFullWindowSurface()) { + mFullWindowSurface.attachInput(win); + } else { + for (LetterboxSurface surface : mSurfaces) { + surface.attachInput(win); + } } } @@ -208,20 +228,61 @@ public class Letterbox { surface.mInputInterceptor.mWindowHandle.displayId = displayId; } } + if (mFullWindowSurface.mInputInterceptor != null) { + mFullWindowSurface.mInputInterceptor.mWindowHandle.displayId = displayId; + } + } + + /** + * Returns {@code true} when using {@link #mFullWindowSurface} instead of {@link mSurfaces}. + */ + private boolean useFullWindowSurface() { + return mAreCornersRounded.get() || mHasWallpaperBackgroundSupplier.get(); + } + + private final class TapEventReceiver extends InputEventReceiver { + + private final GestureDetector mDoubleTapDetector; + private final DoubleTapListener mDoubleTapListener; + + TapEventReceiver(InputChannel inputChannel, Context context) { + super(inputChannel, UiThread.getHandler().getLooper()); + mDoubleTapListener = new DoubleTapListener(); + mDoubleTapDetector = new GestureDetector( + context, mDoubleTapListener, UiThread.getHandler()); + } + + @Override + public void onInputEvent(InputEvent event) { + final MotionEvent motionEvent = (MotionEvent) event; + finishInputEvent(event, mDoubleTapDetector.onTouchEvent(motionEvent)); + } + } + + private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener { + @Override + public boolean onDoubleTapEvent(MotionEvent e) { + if (e.getAction() == MotionEvent.ACTION_UP) { + mDoubleTapCallback.accept((int) e.getX()); + return true; + } + return false; + } } - private static class InputInterceptor { - final InputChannel mClientChannel; - final InputWindowHandle mWindowHandle; - final InputEventReceiver mInputEventReceiver; - final WindowManagerService mWmService; - final IBinder mToken; + private final class InputInterceptor { + + private final InputChannel mClientChannel; + private final InputWindowHandle mWindowHandle; + private final InputEventReceiver mInputEventReceiver; + private final WindowManagerService mWmService; + private final IBinder mToken; InputInterceptor(String namePrefix, WindowState win) { mWmService = win.mWmService; final String name = namePrefix + (win.mActivityRecord != null ? win.mActivityRecord : win); mClientChannel = mWmService.mInputManager.createInputChannel(name); - mInputEventReceiver = new SimpleInputReceiver(mClientChannel); + mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService.mContext); mToken = mClientChannel.getToken(); @@ -259,12 +320,6 @@ public class Letterbox { mInputEventReceiver.dispose(); mClientChannel.dispose(); } - - private static class SimpleInputReceiver extends InputEventReceiver { - SimpleInputReceiver(InputChannel inputChannel) { - super(inputChannel, UiThread.getHandler().getLooper()); - } - } } private class LetterboxSurface { @@ -308,6 +363,10 @@ public class Letterbox { mInputInterceptor = new InputInterceptor("Letterbox_" + mType + "_", win); } + boolean isRemoved() { + return mSurface != null || mInputInterceptor != null; + } + public void remove() { if (mSurface != null) { mTransactionFactory.get().remove(mSurface).apply(); diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index 7174e68b06f4..e8490c5873ef 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -17,11 +17,11 @@ package com.android.server.wm; import android.annotation.IntDef; +import android.annotation.Nullable; import android.content.Context; import android.graphics.Color; import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -30,8 +30,7 @@ import java.lang.annotation.RetentionPolicy; final class LetterboxConfiguration { /** - * Override of aspect ratio for fixed orientation letterboxing that is set via ADB with - * set-fixed-orientation-letterbox-aspect-ratio or via {@link + * Override of aspect ratio for fixed orientation letterboxing that is set via {@link * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored * if it is <= this value. */ @@ -54,6 +53,27 @@ final class LetterboxConfiguration { /** Using wallpaper as a background which can be blurred or dimmed with dark scrim. */ static final int LETTERBOX_BACKGROUND_WALLPAPER = 3; + /** + * Enum for Letterbox reachability position types. + * + * <p>Order from left to right is important since it's used in {@link + * #movePositionForReachabilityToNextRightStop} and {@link + * #movePositionForReachabilityToNextLeftStop}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({LETTERBOX_REACHABILITY_POSITION_LEFT, LETTERBOX_REACHABILITY_POSITION_CENTER, + LETTERBOX_REACHABILITY_POSITION_RIGHT}) + @interface LetterboxReachabilityPosition {}; + + /** Letterboxed app window is aligned to the left side. */ + static final int LETTERBOX_REACHABILITY_POSITION_LEFT = 0; + + /** Letterboxed app window is positioned in the horizontal center. */ + static final int LETTERBOX_REACHABILITY_POSITION_CENTER = 1; + + /** Letterboxed app window is aligned to the right side. */ + static final int LETTERBOX_REACHABILITY_POSITION_RIGHT = 2; + final Context mContext; // Aspect ratio of letterbox for fixed orientation, values <= @@ -64,7 +84,10 @@ final class LetterboxConfiguration { private int mLetterboxActivityCornersRadius; // Color for {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} letterbox background type. - private Color mLetterboxBackgroundColor; + @Nullable private Color mLetterboxBackgroundColorOverride; + + // Color resource id for {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} letterbox background type. + @Nullable private Integer mLetterboxBackgroundColorResourceIdOverride; @LetterboxBackgroundType private int mLetterboxBackgroundType; @@ -82,21 +105,43 @@ final class LetterboxConfiguration { // side of the screen and 1.0 to the right side. private float mLetterboxHorizontalPositionMultiplier; - LetterboxConfiguration(Context context) { - mContext = context; - mFixedOrientationLetterboxAspectRatio = context.getResources().getFloat( + // Default horizontal position the letterboxed app window when reachability is enabled and + // an app is fullscreen in landscape device orientatio. + // It is used as a starting point for mLetterboxPositionForReachability. + @LetterboxReachabilityPosition + private int mDefaultPositionForReachability; + + // Whether reachability repositioning is allowed for letterboxed fullscreen apps in landscape + // device orientation. + private boolean mIsReachabilityEnabled; + + // Horizontal position of a center of the letterboxed app window which is global to prevent + // "jumps" when switching between letterboxed apps. It's updated to reposition the app window + // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in + // LetterboxUiController#getHorizontalPositionMultiplier which is called from + // ActivityRecord#updateResolvedBoundsHorizontalPosition. + // TODO(b/199426138): Global reachability setting causes a jump when resuming an app from + // Overview after changing position in another app. + @LetterboxReachabilityPosition + private volatile int mLetterboxPositionForReachability; + + LetterboxConfiguration(Context systemUiContext) { + mContext = systemUiContext; + mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat( R.dimen.config_fixedOrientationLetterboxAspectRatio); - mLetterboxActivityCornersRadius = context.getResources().getInteger( + mLetterboxActivityCornersRadius = mContext.getResources().getInteger( R.integer.config_letterboxActivityCornersRadius); - mLetterboxBackgroundColor = Color.valueOf(context.getResources().getColor( - R.color.config_letterboxBackgroundColor)); - mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(context); - mLetterboxBackgroundWallpaperBlurRadius = context.getResources().getDimensionPixelSize( + mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext); + mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize( R.dimen.config_letterboxBackgroundWallpaperBlurRadius); - mLetterboxBackgroundWallpaperDarkScrimAlpha = context.getResources().getFloat( + mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat( R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha); - mLetterboxHorizontalPositionMultiplier = context.getResources().getFloat( + mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat( R.dimen.config_letterboxHorizontalPositionMultiplier); + mIsReachabilityEnabled = mContext.getResources().getBoolean( + R.bool.config_letterboxIsReachabilityEnabled); + mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext); + mLetterboxPositionForReachability = mDefaultPositionForReachability; } /** @@ -105,12 +150,20 @@ final class LetterboxConfiguration { * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and * the framework implementation will be used to determine the aspect ratio. */ - @VisibleForTesting void setFixedOrientationLetterboxAspectRatio(float aspectRatio) { mFixedOrientationLetterboxAspectRatio = aspectRatio; } /** + * Resets the aspect ratio of letterbox for fixed orientation to {@link + * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}. + */ + void resetFixedOrientationLetterboxAspectRatio() { + mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat( + com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio); + } + + /** * Gets the aspect ratio of letterbox for fixed orientation. */ float getFixedOrientationLetterboxAspectRatio() { @@ -118,10 +171,29 @@ final class LetterboxConfiguration { } /** + * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0, + * both it and a value of {@link + * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and + * corners of the activity won't be rounded. + */ + void setLetterboxActivityCornersRadius(int cornersRadius) { + mLetterboxActivityCornersRadius = cornersRadius; + } + + /** + * Resets corners raidus for activities presented in the letterbox mode to {@link + * com.android.internal.R.integer.config_letterboxActivityCornersRadius}. + */ + void resetLetterboxActivityCornersRadius() { + mLetterboxActivityCornersRadius = mContext.getResources().getInteger( + com.android.internal.R.integer.config_letterboxActivityCornersRadius); + } + + /** * Whether corners of letterboxed activities are rounded. */ boolean isLetterboxActivityCornersRounded() { - return getLetterboxActivityCornersRadius() > 0; + return getLetterboxActivityCornersRadius() != 0; } /** @@ -132,23 +204,72 @@ final class LetterboxConfiguration { } /** - * Gets color of letterbox background which is used when {@link + * Gets color of letterbox background which is used when {@link * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as * fallback for other backfround types. */ Color getLetterboxBackgroundColor() { - return mLetterboxBackgroundColor; + if (mLetterboxBackgroundColorOverride != null) { + return mLetterboxBackgroundColorOverride; + } + int colorId = mLetterboxBackgroundColorResourceIdOverride != null + ? mLetterboxBackgroundColorResourceIdOverride + : R.color.config_letterboxBackgroundColor; + // Query color dynamically because material colors extracted from wallpaper are updated + // when wallpaper is changed. + return Color.valueOf(mContext.getResources().getColor(colorId)); + } + + + /** + * Sets color of letterbox background which is used when {@link + * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as + * fallback for other backfround types. + */ + void setLetterboxBackgroundColor(Color color) { + mLetterboxBackgroundColorOverride = color; + } + + /** + * Sets color ID of letterbox background which is used when {@link + * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as + * fallback for other backfround types. + */ + void setLetterboxBackgroundColorResourceId(int colorId) { + mLetterboxBackgroundColorResourceIdOverride = colorId; + } + + /** + * Resets color of letterbox background to {@link + * com.android.internal.R.color.config_letterboxBackgroundColor}. + */ + void resetLetterboxBackgroundColor() { + mLetterboxBackgroundColorOverride = null; + mLetterboxBackgroundColorResourceIdOverride = null; } /** * Gets {@link LetterboxBackgroundType} specified in {@link - * com.android.internal.R.integer.config_letterboxBackgroundType} or over via ADB command. + * com.android.internal.R.integer.config_letterboxBackgroundType}. */ @LetterboxBackgroundType int getLetterboxBackgroundType() { return mLetterboxBackgroundType; } + /** Sets letterbox background type. */ + void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) { + mLetterboxBackgroundType = backgroundType; + } + + /** + * Resets cletterbox background type to {@link + * com.android.internal.R.integer.config_letterboxBackgroundType}. + */ + void resetLetterboxBackgroundType() { + mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext); + } + /** Returns a string representing the given {@link LetterboxBackgroundType}. */ static String letterboxBackgroundTypeToString( @LetterboxBackgroundType int backgroundType) { @@ -178,6 +299,27 @@ final class LetterboxConfiguration { } /** + * Overrides alpha of a black scrim shown over wallpaper for {@link + * #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link mLetterboxBackgroundType}. + * + * <p>If given value is < 0 or >= 1, both it and a value of {@link + * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha} are ignored + * and 0.0 (transparent) is instead. + */ + void setLetterboxBackgroundWallpaperDarkScrimAlpha(float alpha) { + mLetterboxBackgroundWallpaperDarkScrimAlpha = alpha; + } + + /** + * Resets alpha of a black scrim shown over wallpaper letterbox background to {@link + * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha}. + */ + void resetLetterboxBackgroundWallpaperDarkScrimAlpha() { + mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat( + com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha); + } + + /** * Gets alpha of a black scrim shown over wallpaper letterbox background. */ float getLetterboxBackgroundWallpaperDarkScrimAlpha() { @@ -185,6 +327,28 @@ final class LetterboxConfiguration { } /** + * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in + * {@link mLetterboxBackgroundType}. + * + * <p> If given value <= 0, both it and a value of {@link + * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius} are ignored + * and 0 is used instead. + */ + void setLetterboxBackgroundWallpaperBlurRadius(int radius) { + mLetterboxBackgroundWallpaperBlurRadius = radius; + } + + /** + * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link + * mLetterboxBackgroundType} to {@link + * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius}. + */ + void resetLetterboxBackgroundWallpaperBlurRadius() { + mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius); + } + + /** * Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link * mLetterboxBackgroundType}. */ @@ -194,15 +358,14 @@ final class LetterboxConfiguration { /* * Gets horizontal position of a center of the letterboxed app window specified - * in {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} - * or via an ADB command. 0 corresponds to the left side of the screen and 1 to the - * right side. - * - * <p>This value can be outside of [0, 1] range so clients need to check and default to the - * central position (0.5). + * in {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}. + * 0 corresponds to the left side of the screen and 1 to the right side. */ float getLetterboxHorizontalPositionMultiplier() { - return mLetterboxHorizontalPositionMultiplier; + return (mLetterboxHorizontalPositionMultiplier < 0.0f + || mLetterboxHorizontalPositionMultiplier > 1.0f) + // Default to central position if invalid value is provided. + ? 0.5f : mLetterboxHorizontalPositionMultiplier; } /** @@ -211,9 +374,128 @@ final class LetterboxConfiguration { * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and * central position (0.5) is used. */ - @VisibleForTesting void setLetterboxHorizontalPositionMultiplier(float multiplier) { mLetterboxHorizontalPositionMultiplier = multiplier; } + /** + * Resets horizontal position of a center of the letterboxed app window to {@link + * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}. + */ + void resetLetterboxHorizontalPositionMultiplier() { + mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat( + com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier); + } + + /* + * Whether reachability repositioning is allowed for letterboxed fullscreen apps in landscape + * device orientation. + */ + boolean getIsReachabilityEnabled() { + return mIsReachabilityEnabled; + } + + /** + * Overrides whether reachability repositioning is allowed for letterboxed fullscreen apps in + * landscape device orientation. + */ + void setIsReachabilityEnabled(boolean enabled) { + mIsReachabilityEnabled = enabled; + } + + /** + * Resets whether reachability repositioning is allowed for letterboxed fullscreen apps in + * landscape device orientation to {@link R.bool.config_letterboxIsReachabilityEnabled}. + */ + void resetIsReachabilityEnabled() { + mIsReachabilityEnabled = mContext.getResources().getBoolean( + R.bool.config_letterboxIsReachabilityEnabled); + } + + /* + * Gets default horizontal position of the letterboxed app window when reachability is enabled. + * Specified in {@link R.integer.config_letterboxDefaultPositionForReachability}. + */ + @LetterboxReachabilityPosition + int getDefaultPositionForReachability() { + return mDefaultPositionForReachability; + } + + /** + * Overrides default horizonal position of the letterboxed app window when reachability + * is enabled. + */ + void setDefaultPositionForReachability(@LetterboxReachabilityPosition int position) { + mDefaultPositionForReachability = position; + } + + /** + * Resets default horizontal position of the letterboxed app window when reachability is + * enabled to {@link R.integer.config_letterboxDefaultPositionForReachability}. + */ + void resetDefaultPositionForReachability() { + mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext); + } + + @LetterboxReachabilityPosition + private static int readLetterboxReachabilityPositionFromConfig(Context context) { + int position = context.getResources().getInteger( + R.integer.config_letterboxDefaultPositionForReachability); + return position == LETTERBOX_REACHABILITY_POSITION_LEFT + || position == LETTERBOX_REACHABILITY_POSITION_CENTER + || position == LETTERBOX_REACHABILITY_POSITION_RIGHT + ? position : LETTERBOX_REACHABILITY_POSITION_CENTER; + } + + /* + * Gets horizontal position of a center of the letterboxed app window when reachability + * is enabled specified. 0 corresponds to the left side of the screen and 1 to the right side. + * + * <p>The position multiplier is changed after each double tap in the letterbox area. + */ + float getHorizontalMultiplierForReachability() { + switch (mLetterboxPositionForReachability) { + case LETTERBOX_REACHABILITY_POSITION_LEFT: + return 0.0f; + case LETTERBOX_REACHABILITY_POSITION_CENTER: + return 0.5f; + case LETTERBOX_REACHABILITY_POSITION_RIGHT: + return 1.0f; + default: + throw new AssertionError( + "Unexpected letterbox position type: " + mLetterboxPositionForReachability); + } + } + + /** Returns a string representing the given {@link LetterboxReachabilityPosition}. */ + static String letterboxReachabilityPositionToString( + @LetterboxReachabilityPosition int position) { + switch (position) { + case LETTERBOX_REACHABILITY_POSITION_LEFT: + return "LETTERBOX_REACHABILITY_POSITION_LEFT"; + case LETTERBOX_REACHABILITY_POSITION_CENTER: + return "LETTERBOX_REACHABILITY_POSITION_CENTER"; + case LETTERBOX_REACHABILITY_POSITION_RIGHT: + return "LETTERBOX_REACHABILITY_POSITION_RIGHT"; + default: + throw new AssertionError( + "Unexpected letterbox position type: " + position); + } + } + + /** + * Changes letterbox position for reachability to the next available one on the right side. + */ + void movePositionForReachabilityToNextRightStop() { + mLetterboxPositionForReachability = Math.min( + mLetterboxPositionForReachability + 1, LETTERBOX_REACHABILITY_POSITION_RIGHT); + } + + /** + * Changes letterbox position for reachability to the next available one on the left side. + */ + void movePositionForReachabilityToNextLeftStop() { + mLetterboxPositionForReachability = Math.max(mLetterboxPositionForReachability - 1, 0); + } + } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index b6b8ad14e106..8866343afe03 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -16,8 +16,12 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; +import static com.android.server.wm.ActivityRecord.computeAspectRatio; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; @@ -28,14 +32,20 @@ import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTy import android.annotation.Nullable; import android.app.ActivityManager.TaskDescription; +import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.util.Slog; +import android.view.InsetsSource; +import android.view.InsetsState; +import android.view.RoundedCorner; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.WindowManager; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; @@ -55,6 +65,10 @@ final class LetterboxUiController { private final LetterboxConfiguration mLetterboxConfiguration; private final ActivityRecord mActivityRecord; + // Taskbar expanded height. Used to determine whether to crop an app window to display rounded + // corners above the taskbar. + private float mExpandedTaskBarHeight; + private boolean mShowWallpaperForLetterboxBackground; @Nullable @@ -66,6 +80,8 @@ final class LetterboxUiController { // is created in its constructor. It shouldn't be used in this constructor but it's safe // to use it after since controller is only used in ActivityRecord. mActivityRecord = activityRecord; + mExpandedTaskBarHeight = + getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height); } /** Cleans up {@link Letterbox} if it exists.*/ @@ -134,11 +150,12 @@ final class LetterboxUiController { if (mLetterbox == null) { mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null), mActivityRecord.mWmService.mTransactionFactory, - mLetterboxConfiguration::isLetterboxActivityCornersRounded, + this::shouldLetterboxHaveRoundedCorners, this::getLetterboxBackgroundColor, this::hasWallpaperBackgroudForLetterbox, this::getLetterboxWallpaperBlurRadius, - this::getLetterboxWallpaperDarkScrimAlpha); + this::getLetterboxWallpaperDarkScrimAlpha, + this::handleDoubleTap); mLetterbox.attachInput(w); } mActivityRecord.getPosition(mTmpPoint); @@ -158,11 +175,95 @@ final class LetterboxUiController { } } + private boolean shouldLetterboxHaveRoundedCorners() { + // TODO(b/214030873): remove once background is drawn for transparent activities + // Letterbox shouldn't have rounded corners if the activity is transparent + return mLetterboxConfiguration.isLetterboxActivityCornersRounded() + && mActivityRecord.fillsParent(); + } + + float getHorizontalPositionMultiplier(Configuration parentConfiguration) { + // Don't check resolved configuration because it may not be updated yet during + // configuration change. + return isReachabilityEnabled(parentConfiguration) + // Using the last global dynamic position to avoid "jumps" when moving + // between apps or activities. + ? mLetterboxConfiguration.getHorizontalMultiplierForReachability() + : mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(); + } + + float getFixedOrientationLetterboxAspectRatio(Configuration parentConfiguration) { + // Don't check resolved windowing mode because it may not be updated yet during + // configuration change. + if (!isReachabilityEnabled(parentConfiguration)) { + return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(); + } + + int dividerWindowWidth = + getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness); + int dividerInsets = + getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets); + int dividerSize = dividerWindowWidth - dividerInsets * 2; + + // Getting the same aspect ratio that apps get in split screen. + Rect bounds = new Rect(parentConfiguration.windowConfiguration.getAppBounds()); + bounds.inset(dividerSize, /* dy */ 0); + bounds.right = bounds.centerX(); + + return computeAspectRatio(bounds); + } + + Resources getResources() { + return mActivityRecord.mWmService.mContext.getResources(); + } + + private void handleDoubleTap(int x) { + if (!isReachabilityEnabled() || mActivityRecord.isInTransition()) { + return; + } + + if (mLetterbox.getInnerFrame().left <= x && mLetterbox.getInnerFrame().right >= x) { + // Only react to clicks at the sides of the letterboxed app window. + return; + } + + if (mLetterbox.getInnerFrame().left > x) { + // Moving to the next stop on the left side of the app window: right > center > left. + mLetterboxConfiguration.movePositionForReachabilityToNextLeftStop(); + } else if (mLetterbox.getInnerFrame().right < x) { + // Moving to the next stop on the right side of the app window: left > center > right. + mLetterboxConfiguration.movePositionForReachabilityToNextRightStop(); + } + + // TODO(197549949): Add animation for transition. + mActivityRecord.recomputeConfiguration(); + } + + /** + * Whether reachability is enabled for an activity in the curren configuration. + * + * <p>Conditions that needs to be met: + * <ul> + * <li>Activity is portrait-only. + * <li>Fullscreen window in landscape device orientation. + * <li>Reachability is enabled. + * </ul> + */ + private boolean isReachabilityEnabled(Configuration parentConfiguration) { + return mLetterboxConfiguration.getIsReachabilityEnabled() + && parentConfiguration.windowConfiguration.getWindowingMode() + == WINDOWING_MODE_FULLSCREEN + && parentConfiguration.orientation == ORIENTATION_LANDSCAPE + && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT; + } + + private boolean isReachabilityEnabled() { + return isReachabilityEnabled(mActivityRecord.getParent().getConfiguration()); + } + @VisibleForTesting boolean shouldShowLetterboxUi(WindowState mainWindow) { return isSurfaceReadyAndVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed() - // Check that an activity isn't transparent. - && mActivityRecord.fillsParent() // Check for FLAG_SHOW_WALLPAPER explicitly instead of using // WindowContainer#showWallpaper because the later will return true when this // activity is using blurred wallpaper for letterbox backgroud. @@ -219,21 +320,72 @@ final class LetterboxUiController { } private void updateRoundedCorners(WindowState mainWindow) { - int cornersRadius = - // Don't round corners if letterboxed only for display cutout. - shouldShowLetterboxUi(mainWindow) - && !mainWindow.isLetterboxedForDisplayCutout() - ? Math.max(0, mLetterboxConfiguration.getLetterboxActivityCornersRadius()) - : 0; - setCornersRadius(mainWindow, cornersRadius); - } - - private void setCornersRadius(WindowState mainWindow, int cornersRadius) { final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface(); if (windowSurface != null && windowSurface.isValid()) { Transaction transaction = mActivityRecord.getSyncTransaction(); - transaction.setCornerRadius(windowSurface, cornersRadius); + + if (!isLetterboxedNotForDisplayCutout(mainWindow) + || !mLetterboxConfiguration.isLetterboxActivityCornersRounded()) { + transaction + .setWindowCrop(windowSurface, null) + .setCornerRadius(windowSurface, 0); + return; + } + + final InsetsState insetsState = mainWindow.getInsetsState(); + final InsetsSource taskbarInsetsSource = + insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + + Rect cropBounds = null; + + // Rounded corners should be displayed above the taskbar. When taskbar is hidden, + // an insets frame is equal to a navigation bar which shouldn't affect position of + // rounded corners since apps are expected to handle navigation bar inset. + // This condition checks whether the taskbar is visible. + if (taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) { + cropBounds = new Rect(mActivityRecord.getBounds()); + // Activity bounds are in screen coordinates while (0,0) for activity's surface + // control is at the top left corner of an app window so offsetting bounds + // accordingly. + cropBounds.offsetTo(0, 0); + // Rounded cornerners should be displayed above the taskbar. + cropBounds.bottom = + Math.min(cropBounds.bottom, taskbarInsetsSource.getFrame().top); + if (mActivityRecord.inSizeCompatMode() + && mActivityRecord.getSizeCompatScale() < 1.0f) { + cropBounds.scale(1.0f / mActivityRecord.getSizeCompatScale()); + } + } + + transaction + .setWindowCrop(windowSurface, cropBounds) + .setCornerRadius(windowSurface, getRoundedCorners(insetsState)); + } + } + + // Returns rounded corners radius based on override in + // R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii. + // Device corners can be different on the right and left sides but we use the same radius + // for all corners for consistency and pick a minimal bottom one for consistency with a + // taskbar rounded corners. + private int getRoundedCorners(InsetsState insetsState) { + if (mLetterboxConfiguration.getLetterboxActivityCornersRadius() >= 0) { + return mLetterboxConfiguration.getLetterboxActivityCornersRadius(); } + return Math.min( + getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT), + getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT)); + } + + private int getInsetsStateCornerRadius( + InsetsState insetsState, @RoundedCorner.Position int position) { + RoundedCorner corner = insetsState.getRoundedCorners().getRoundedCorner(position); + return corner == null ? 0 : corner.getRadius(); + } + + private boolean isLetterboxedNotForDisplayCutout(WindowState mainWindow) { + return shouldShowLetterboxUi(mainWindow) + && !mainWindow.isLetterboxedForDisplayCutout(); } private void updateWallpaperForLetterbox(WindowState mainWindow) { @@ -241,9 +393,8 @@ final class LetterboxUiController { mLetterboxConfiguration.getLetterboxBackgroundType(); boolean wallpaperShouldBeShown = letterboxBackgroundType == LETTERBOX_BACKGROUND_WALLPAPER - && shouldShowLetterboxUi(mainWindow) // Don't use wallpaper as a background if letterboxed for display cutout. - && !mainWindow.isLetterboxedForDisplayCutout() + && isLetterboxedNotForDisplayCutout(mainWindow) // Check that dark scrim alpha or blur radius are provided && (getLetterboxWallpaperBlurRadius() > 0 || getLetterboxWallpaperDarkScrimAlpha() > 0) @@ -285,7 +436,7 @@ final class LetterboxUiController { } pw.println(prefix + " letterboxReason=" + getLetterboxReasonString(mainWin)); - pw.println(prefix + " letterboxAspectRatio=" + pw.println(prefix + " activityAspectRatio=" + mActivityRecord.computeAspectRatio(mActivityRecord.getBounds())); boolean shouldShowLetterboxUi = shouldShowLetterboxUi(mainWin); @@ -299,6 +450,8 @@ final class LetterboxUiController { pw.println(prefix + " letterboxBackgroundType=" + letterboxBackgroundTypeToString( mLetterboxConfiguration.getLetterboxBackgroundType())); + pw.println(prefix + " letterboxCornerRadius=" + + getRoundedCorners(mainWin.getInsetsState())); if (mLetterboxConfiguration.getLetterboxBackgroundType() == LETTERBOX_BACKGROUND_WALLPAPER) { pw.println(prefix + " isLetterboxWallpaperBlurSupported=" @@ -308,8 +461,13 @@ final class LetterboxUiController { pw.println(prefix + " letterboxBackgroundWallpaperBlurRadius=" + getLetterboxWallpaperBlurRadius()); } + + pw.println(prefix + " isReachabilityEnabled=" + isReachabilityEnabled()); pw.println(prefix + " letterboxHorizontalPositionMultiplier=" - + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier()); + + getHorizontalPositionMultiplier(mActivityRecord.getParent().getConfiguration())); + pw.println(prefix + " fixedOrientationLetterboxAspectRatio=" + + getFixedOrientationLetterboxAspectRatio( + mActivityRecord.getParent().getConfiguration())); } /** diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java index 520bd8b2108e..a3eb980992c7 100644 --- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static com.android.server.wm.AnimationAdapterProto.LOCAL; import static com.android.server.wm.LocalAnimationAdapterProto.ANIMATION_SPEC; +import android.annotation.NonNull; import android.os.SystemClock; import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl; @@ -51,7 +52,7 @@ class LocalAnimationAdapter implements AnimationAdapter { @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, - @AnimationType int type, OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) { mAnimator.startAnimation(mSpec, animationLeash, t, () -> finishCallback.onAnimationFinished(type, this)); } diff --git a/services/core/java/com/android/server/wm/LocaleOverlayHelper.java b/services/core/java/com/android/server/wm/LocaleOverlayHelper.java new file mode 100644 index 000000000000..a1a01dba769a --- /dev/null +++ b/services/core/java/com/android/server/wm/LocaleOverlayHelper.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 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 android.os.LocaleList; + +import java.util.Locale; + +/** + * Static utilities to overlay locales on top of another localeList. + * + * <p>This is used to overlay application-specific locales in + * {@link com.android.server.wm.ActivityTaskManagerInternal.PackageConfigurationUpdater} on top of + * system locales. + */ +final class LocaleOverlayHelper { + + /** + * Combines the overlay locales and base locales. + * @return the combined {@link LocaleList} if the overlay locales is not empty/null else + * returns the empty/null LocaleList. + */ + static LocaleList combineLocalesIfOverlayExists(LocaleList overlayLocales, + LocaleList baseLocales) { + if (overlayLocales == null || overlayLocales.isEmpty()) { + return overlayLocales; + } + return combineLocales(overlayLocales, baseLocales); + } + + /** + * Creates a combined {@link LocaleList} by placing overlay locales before base locales and + * dropping duplicates from the base locales. + */ + private static LocaleList combineLocales(LocaleList overlayLocales, LocaleList baseLocales) { + Locale[] combinedLocales = new Locale[overlayLocales.size() + baseLocales.size()]; + for (int i = 0; i < overlayLocales.size(); i++) { + combinedLocales[i] = overlayLocales.get(i); + } + for (int i = 0; i < baseLocales.size(); i++) { + combinedLocales[i + overlayLocales.size()] = baseLocales.get(i); + } + // Constructor of {@link LocaleList} removes duplicates + return new LocaleList(combinedLocales); + } + + +} diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index 94a175caba22..8a2d11636fe3 100644 --- a/services/core/java/com/android/server/wm/LockTaskController.java +++ b/services/core/java/com/android/server/wm/LockTaskController.java @@ -251,15 +251,47 @@ public class LockTaskController { */ boolean activityBlockedFromFinish(ActivityRecord activity) { final Task task = activity.getTask(); - if (activity == task.getRootActivity() - && activity == task.getTopNonFinishingActivity() - && task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV - && isRootTask(task)) { - Slog.i(TAG, "Not finishing task in lock task mode"); - showLockTaskToast(); - return true; + if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV || !isRootTask(task)) { + return false; } - return false; + + final ActivityRecord taskTop = task.getTopNonFinishingActivity(); + final ActivityRecord taskRoot = task.getRootActivity(); + // If task has more than one Activity, verify if there's only adjacent TaskFragments that + // should be finish together in the Task. + if (activity != taskRoot || activity != taskTop) { + final TaskFragment taskFragment = activity.getTaskFragment(); + final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); + if (taskFragment.asTask() != null + || !taskFragment.isDelayLastActivityRemoval() + || adjacentTaskFragment == null) { + // Don't block activity from finishing if the TaskFragment don't have any adjacent + // TaskFragment, or it won't finish together with its adjacent TaskFragment. + return false; + } + + final boolean hasOtherActivityInTaskFragment = + taskFragment.getActivity(a -> !a.finishing && a != activity) != null; + if (hasOtherActivityInTaskFragment) { + // Don't block activity from finishing if there's other Activity in the same + // TaskFragment. + return false; + } + + final boolean hasOtherActivityInTask = task.getActivity(a -> !a.finishing + && a != activity && a.getTaskFragment() != adjacentTaskFragment) != null; + if (hasOtherActivityInTask) { + // Do not block activity from finishing if there are another running activities + // after the current and adjacent TaskFragments are removed. Note that we don't + // check activities in adjacent TaskFragment because it will be finished together + // with TaskFragment regardless of numbers of activities. + return false; + } + } + + Slog.i(TAG, "Not finishing task in lock task mode"); + showLockTaskToast(); + return true; } /** diff --git a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java index e50dc51c402d..7abf3b820c18 100644 --- a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java +++ b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; +import android.annotation.NonNull; import android.view.SurfaceControl; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; @@ -144,7 +145,7 @@ public class NavBarFadeAnimationController extends FadeAnimationController{ @Override public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, - int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { super.startAnimation(animationLeash, t, type, finishCallback); if (mParent != null && mParent.isValid()) { t.reparent(animationLeash, mParent); diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java index d230936b5d51..7c35a2163d6d 100644 --- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java @@ -27,6 +27,7 @@ import static com.android.server.wm.AnimationAdapterProto.REMOTE; import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; +import android.annotation.NonNull; import android.graphics.Rect; import android.os.SystemClock; import android.util.proto.ProtoOutputStream; @@ -68,25 +69,32 @@ class NonAppWindowAnimationAdapter implements AnimationAdapter { long durationHint, long statusBarTransitionDelay, ArrayList<NonAppWindowAnimationAdapter> adaptersOut) { final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); - if (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY - || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER) { + if (shouldStartNonAppWindowAnimationsForKeyguardExit(transit)) { startNonAppWindowAnimationsForKeyguardExit( service, durationHint, statusBarTransitionDelay, targets, adaptersOut); - } else if (transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT - || transit == TRANSIT_OLD_WALLPAPER_CLOSE) { - final boolean shouldAttachNavBarToApp = - displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition() - && service.getRecentsAnimationController() == null - && displayContent.getFadeRotationAnimationController() == null; - if (shouldAttachNavBarToApp) { - startNavigationBarWindowAnimation( - displayContent, durationHint, statusBarTransitionDelay, targets, - adaptersOut); - } + } else if (shouldAttachNavBarToApp(service, displayContent, transit)) { + startNavigationBarWindowAnimation( + displayContent, durationHint, statusBarTransitionDelay, targets, + adaptersOut); } return targets.toArray(new RemoteAnimationTarget[targets.size()]); } + static boolean shouldStartNonAppWindowAnimationsForKeyguardExit( + @WindowManager.TransitionOldType int transit) { + return transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY + || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER; + } + + static boolean shouldAttachNavBarToApp(WindowManagerService service, + DisplayContent displayContent, @WindowManager.TransitionOldType int transit) { + return (transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT + || transit == TRANSIT_OLD_WALLPAPER_CLOSE) + && displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition() + && service.getRecentsAnimationController() == null + && displayContent.getFadeRotationAnimationController() == null; + } + /** * Creates and starts remote animations for all the visible non app windows. * @@ -138,14 +146,14 @@ class NonAppWindowAnimationAdapter implements AnimationAdapter { mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false, new Rect(), null, mWindowContainer.getPrefixOrderIndex(), mWindowContainer.getLastSurfacePosition(), mWindowContainer.getBounds(), null, - mWindowContainer.getWindowConfiguration(), true, null, null, null, + mWindowContainer.getWindowConfiguration(), true, null, null, null, false, mWindowContainer.getWindowType()); return mTarget; } @Override public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, - int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation"); mCapturedLeash = animationLeash; mCapturedLeashFinishCallback = finishCallback; diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java index 1552a96d699a..fe21e5f0011e 100644 --- a/services/core/java/com/android/server/wm/PackageConfigPersister.java +++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java @@ -16,11 +16,10 @@ package com.android.server.wm; -import static android.app.UiModeManager.MODE_NIGHT_AUTO; -import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; - import android.annotation.NonNull; +import android.content.res.Configuration; import android.os.Environment; +import android.os.LocaleList; import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; @@ -54,12 +53,14 @@ public class PackageConfigPersister { private static final String TAG_CONFIG = "config"; private static final String ATTR_PACKAGE_NAME = "package_name"; private static final String ATTR_NIGHT_MODE = "night_mode"; + private static final String ATTR_LOCALES = "locale_list"; private static final String PACKAGE_DIRNAME = "package_configs"; private static final String SUFFIX_FILE_NAME = "_config.xml"; private final PersisterQueue mPersisterQueue; private final Object mLock = new Object(); + private final ActivityTaskManagerService mAtm; @GuardedBy("mLock") private final SparseArray<HashMap<String, PackageConfigRecord>> mPendingWrite = @@ -72,8 +73,9 @@ public class PackageConfigPersister { return new File(Environment.getDataSystemCeDirectory(userId), PACKAGE_DIRNAME); } - PackageConfigPersister(PersisterQueue queue) { + PackageConfigPersister(PersisterQueue queue, ActivityTaskManagerService atm) { mPersisterQueue = queue; + mAtm = atm; } @GuardedBy("mLock") @@ -100,7 +102,8 @@ public class PackageConfigPersister { final TypedXmlPullParser in = Xml.resolvePullParser(is); int event; String packageName = null; - int nightMode = MODE_NIGHT_AUTO; + Integer nightMode = null; + LocaleList locales = null; while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && event != XmlPullParser.END_TAG) { final String name = in.getName(); @@ -120,6 +123,9 @@ public class PackageConfigPersister { case ATTR_NIGHT_MODE: nightMode = Integer.parseInt(attrValue); break; + case ATTR_LOCALES: + locales = LocaleList.forLanguageTags(attrValue); + break; } } } @@ -130,6 +136,7 @@ public class PackageConfigPersister { final PackageConfigRecord initRecord = findRecordOrCreate(mModified, packageName, userId); initRecord.mNightMode = nightMode; + initRecord.mLocales = locales; if (DEBUG) { Slog.d(TAG, "loadPackages: load one package " + initRecord); } @@ -155,20 +162,28 @@ public class PackageConfigPersister { "updateConfigIfNeeded record " + container + " find? " + modifiedRecord); } if (modifiedRecord != null) { - container.setOverrideNightMode(modifiedRecord.mNightMode); + container.applyAppSpecificConfig(modifiedRecord.mNightMode, + LocaleOverlayHelper.combineLocalesIfOverlayExists( + modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales())); } } } @GuardedBy("mLock") void updateFromImpl(String packageName, int userId, - ActivityTaskManagerService.PackageConfigurationUpdaterImpl impl) { + PackageConfigurationUpdaterImpl impl) { synchronized (mLock) { PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId); - record.mNightMode = impl.getNightMode(); - - if (record.isResetNightMode()) { - removePackage(record.mName, record.mUserId); + if (impl.getNightMode() != null) { + record.mNightMode = impl.getNightMode(); + } + if (impl.getLocales() != null) { + record.mLocales = impl.getLocales(); + } + if ((record.mNightMode == null || record.isResetNightMode()) + && (record.mLocales == null || record.mLocales.isEmpty())) { + // if all values default to system settings, we can remove the package. + removePackage(packageName, userId); } else { final PackageConfigRecord pendingRecord = findRecord(mPendingWrite, record.mName, record.mUserId); @@ -179,10 +194,11 @@ public class PackageConfigPersister { } else { writeRecord = pendingRecord; } - if (writeRecord.mNightMode == record.mNightMode) { + + if (!updateNightMode(record, writeRecord) && !updateLocales(record, writeRecord)) { return; } - writeRecord.mNightMode = record.mNightMode; + if (DEBUG) { Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord); } @@ -191,6 +207,22 @@ public class PackageConfigPersister { } } + private boolean updateNightMode(PackageConfigRecord record, PackageConfigRecord writeRecord) { + if (record.mNightMode == null || record.mNightMode.equals(writeRecord.mNightMode)) { + return false; + } + writeRecord.mNightMode = record.mNightMode; + return true; + } + + private boolean updateLocales(PackageConfigRecord record, PackageConfigRecord writeRecord) { + if (record.mLocales == null || record.mLocales.equals(writeRecord.mLocales)) { + return false; + } + writeRecord.mLocales = record.mLocales; + return true; + } + @GuardedBy("mLock") void removeUser(int userId) { synchronized (mLock) { @@ -210,7 +242,7 @@ public class PackageConfigPersister { @GuardedBy("mLock") void onPackageUninstall(String packageName) { synchronized (mLock) { - for (int i = mModified.size() - 1; i > 0; i--) { + for (int i = mModified.size() - 1; i >= 0; i--) { final int userId = mModified.keyAt(i); removePackage(packageName, userId); } @@ -238,11 +270,30 @@ public class PackageConfigPersister { } } + /** + * Retrieves and returns application configuration from persisted records if it exists, else + * returns null. + */ + ActivityTaskManagerInternal.PackageConfig findPackageConfiguration(String packageName, + int userId) { + synchronized (mLock) { + PackageConfigRecord packageConfigRecord = findRecord(mModified, packageName, userId); + if (packageConfigRecord == null) { + Slog.w(TAG, "App-specific configuration not found for packageName: " + packageName + + " and userId: " + userId); + return null; + } + return new ActivityTaskManagerInternal.PackageConfig( + packageConfigRecord.mNightMode, packageConfigRecord.mLocales); + } + } + // store a changed data so we don't need to get the process static class PackageConfigRecord { final String mName; final int mUserId; - int mNightMode; + Integer mNightMode; + LocaleList mLocales; PackageConfigRecord(String name, int userId) { mName = name; @@ -250,13 +301,13 @@ public class PackageConfigPersister { } boolean isResetNightMode() { - return mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM; + return mNightMode == Configuration.UI_MODE_NIGHT_UNDEFINED; } @Override public String toString() { return "PackageConfigRecord package name: " + mName + " userId " + mUserId - + " nightMode " + mNightMode; + + " nightMode " + mNightMode + " locales " + mLocales; } } @@ -369,7 +420,13 @@ public class PackageConfigPersister { } xmlSerializer.startTag(null, TAG_CONFIG); xmlSerializer.attribute(null, ATTR_PACKAGE_NAME, mRecord.mName); - xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode); + if (mRecord.mNightMode != null) { + xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode); + } + if (mRecord.mLocales != null) { + xmlSerializer.attribute(null, ATTR_LOCALES, mRecord.mLocales + .toLanguageTags()); + } xmlSerializer.endTag(null, TAG_CONFIG); xmlSerializer.endDocument(); xmlSerializer.flush(); diff --git a/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java b/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java new file mode 100644 index 000000000000..8bbcf1f9c029 --- /dev/null +++ b/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2021 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 android.content.pm.PackageManager; +import android.os.Binder; +import android.os.LocaleList; +import android.util.ArraySet; +import android.util.Slog; + +import java.util.Optional; + +/** + * An implementation of {@link ActivityTaskManagerInternal.PackageConfigurationUpdater}. + */ +final class PackageConfigurationUpdaterImpl implements + ActivityTaskManagerInternal.PackageConfigurationUpdater { + private static final String TAG = "PackageConfigurationUpdaterImpl"; + private final Optional<Integer> mPid; + private Integer mNightMode; + private LocaleList mLocales; + private String mPackageName; + private int mUserId; + private ActivityTaskManagerService mAtm; + + PackageConfigurationUpdaterImpl(int pid, ActivityTaskManagerService atm) { + mPid = Optional.of(pid); + mAtm = atm; + } + + PackageConfigurationUpdaterImpl(String packageName, int userId, + ActivityTaskManagerService atm) { + mPackageName = packageName; + mUserId = userId; + mAtm = atm; + mPid = Optional.empty(); + } + + @Override + public ActivityTaskManagerInternal.PackageConfigurationUpdater setNightMode(int nightMode) { + synchronized (this) { + mNightMode = nightMode; + } + return this; + } + + @Override + public ActivityTaskManagerInternal.PackageConfigurationUpdater + setLocales(LocaleList locales) { + synchronized (this) { + mLocales = locales; + } + return this; + } + + @Override + public void commit() { + synchronized (this) { + synchronized (mAtm.mGlobalLock) { + final long ident = Binder.clearCallingIdentity(); + try { + final int uid; + if (mPid.isPresent()) { + WindowProcessController wpc = mAtm.mProcessMap.getProcess(mPid.get()); + if (wpc == null) { + Slog.w(TAG, "commit: Override application configuration failed: " + + "cannot find pid " + mPid); + return; + } + uid = wpc.mUid; + mUserId = wpc.mUserId; + mPackageName = wpc.mInfo.packageName; + } else { + uid = mAtm.getPackageManagerInternalLocked().getPackageUid(mPackageName, + /* flags = */ PackageManager.MATCH_ALL, mUserId); + if (uid < 0) { + Slog.w(TAG, "commit: update of application configuration failed: " + + "userId or packageName not valid " + mUserId); + return; + } + } + updateConfig(uid, mPackageName); + mAtm.mPackageConfigPersister.updateFromImpl(mPackageName, mUserId, this); + + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + } + + private void updateConfig(int uid, String packageName) { + final ArraySet<WindowProcessController> processes = mAtm.mProcessMap.getProcesses(uid); + if (processes == null) return; + for (int i = processes.size() - 1; i >= 0; i--) { + final WindowProcessController wpc = processes.valueAt(i); + if (!wpc.mInfo.packageName.equals(packageName)) continue; + LocaleList localesOverride = LocaleOverlayHelper.combineLocalesIfOverlayExists( + mLocales, mAtm.getGlobalConfiguration().getLocales()); + wpc.applyAppSpecificConfig(mNightMode, localesOverride); + wpc.updateAppSpecificSettingsForAllActivities(mNightMode, localesOverride); + } + } + + Integer getNightMode() { + return mNightMode; + } + + LocaleList getLocales() { + return mLocales; + } +} diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java index 7e95e7d2aa8c..1da0fe731709 100644 --- a/services/core/java/com/android/server/wm/PinnedTaskController.java +++ b/services/core/java/com/android/server/wm/PinnedTaskController.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -173,10 +172,8 @@ class PinnedTaskController { * to avoid flickering when running PiP animation across different orientations. */ void deferOrientationChangeForEnteringPipFromFullScreenIfNeeded() { - final Task topFullscreenTask = mDisplayContent.getDefaultTaskDisplayArea() - .getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN); - final ActivityRecord topFullscreen = topFullscreenTask != null - ? topFullscreenTask.topRunningActivity() : null; + final ActivityRecord topFullscreen = mDisplayContent.getActivity( + a -> a.fillsParent() && !a.getTask().inMultiWindowMode()); if (topFullscreen == null || topFullscreen.hasFixedRotationTransform()) { return; } @@ -211,7 +208,9 @@ class PinnedTaskController { } mFreezingTaskConfig = true; mDestRotatedBounds = new Rect(bounds); - continueOrientationChange(); + if (!mDisplayContent.mTransitionController.isShellTransitionsEnabled()) { + continueOrientationChange(); + } } /** @@ -244,7 +243,8 @@ class PinnedTaskController { int oldRotation, int newRotation) { final Rect bounds = mDestRotatedBounds; final PictureInPictureSurfaceTransaction pipTx = mPipTransaction; - if (bounds == null && pipTx == null) { + final boolean emptyPipPositionTx = pipTx == null || pipTx.mPosition == null; + if (bounds == null && emptyPipPositionTx) { return; } final TaskDisplayArea taskArea = mDisplayContent.getDefaultTaskDisplayArea(); @@ -256,25 +256,27 @@ class PinnedTaskController { mDestRotatedBounds = null; mPipTransaction = null; final Rect areaBounds = taskArea.getBounds(); - if (pipTx != null) { + if (!emptyPipPositionTx) { // The transaction from recents animation is in old rotation. So the position needs to // be rotated. - float dx = pipTx.mPositionX; - float dy = pipTx.mPositionY; + float dx = pipTx.mPosition.x; + float dy = pipTx.mPosition.y; final Matrix matrix = pipTx.getMatrix(); if (pipTx.mRotation == 90) { - dx = pipTx.mPositionY; - dy = areaBounds.right - pipTx.mPositionX; + dx = pipTx.mPosition.y; + dy = areaBounds.right - pipTx.mPosition.x; matrix.postRotate(-90); } else if (pipTx.mRotation == -90) { - dx = areaBounds.bottom - pipTx.mPositionY; - dy = pipTx.mPositionX; + dx = areaBounds.bottom - pipTx.mPosition.y; + dy = pipTx.mPosition.x; matrix.postRotate(90); } matrix.postTranslate(dx, dy); final SurfaceControl leash = pinnedTask.getSurfaceControl(); - t.setMatrix(leash, matrix, new float[9]) - .setCornerRadius(leash, pipTx.mCornerRadius); + t.setMatrix(leash, matrix, new float[9]); + if (pipTx.hasCornerRadiusSet()) { + t.setCornerRadius(leash, pipTx.mCornerRadius); + } Slog.i(TAG, "Seamless rotation PiP tx=" + pipTx + " pos=" + dx + "," + dy); return; } @@ -317,15 +319,11 @@ class PinnedTaskController { } /** Resets the states which were used to perform fixed rotation with PiP task. */ - void onCancelFixedRotationTransform(Task task) { + void onCancelFixedRotationTransform() { mFreezingTaskConfig = false; mDeferOrientationChanging = false; mDestRotatedBounds = null; mPipTransaction = null; - if (!task.isOrganized()) { - // Force clearing Task#mForceNotOrganized because the display didn't rotate. - task.onConfigurationChanged(task.getParent().getConfiguration()); - } } /** diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 11b63b4417ab..11d1270b48c3 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -1110,13 +1110,15 @@ class RecentTasks { } if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: trimming tasks for " + task); - removeForAddTask(task); + final int removedIndex = removeForAddTask(task); task.inRecents = true; if (!isAffiliated || needAffiliationFix) { // If this is a simple non-affiliated task, or we had some failure trying to // handle it as part of an affilated task, then just place it at the top. - mTasks.add(0, task); + // But if the list is frozen, adding the task to the removed index to keep the order. + int indexToAdd = mFreezeTaskListReordering && removedIndex != -1 ? removedIndex : 0; + mTasks.add(indexToAdd, task); notifyTaskAdded(task); if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding " + task); } else if (isAffiliated) { @@ -1357,7 +1359,8 @@ class RecentTasks { + " activityType=" + task.getActivityType() + " windowingMode=" + task.getWindowingMode() + " isAlwaysOnTopWhenVisible=" + task.isAlwaysOnTopWhenVisible() - + " intentFlags=" + task.getBaseIntent().getFlags()); + + " intentFlags=" + task.getBaseIntent().getFlags() + + " isEmbedded=" + task.isEmbedded()); } switch (task.getActivityType()) { @@ -1403,6 +1406,11 @@ class RecentTasks { return false; } + // Ignore the task if it is a embedded task + if (task.isEmbedded()) { + return false; + } + return true; } @@ -1495,14 +1503,14 @@ class RecentTasks { * If needed, remove oldest existing entries in recents that are for the same kind * of task as the given one. */ - private void removeForAddTask(Task task) { + private int removeForAddTask(Task task) { // The adding task will be in recents so it is not hidden. mHiddenTasks.remove(task); final int removeIndex = findRemoveIndexForAddTask(task); if (removeIndex == -1) { // Nothing to trim - return; + return removeIndex; } // There is a similar task that will be removed for the addition of {@param task}, but it @@ -1524,6 +1532,7 @@ class RecentTasks { } } notifyTaskPersisterLocked(removedTask, false /* flush */); + return removeIndex; } /** @@ -1531,11 +1540,6 @@ class RecentTasks { * list (if any). */ private int findRemoveIndexForAddTask(Task task) { - if (mFreezeTaskListReordering) { - // Defer removing tasks due to the addition of new tasks until the task list is unfrozen - return -1; - } - final int recentsCount = mTasks.size(); final Intent intent = task.intent; final boolean document = intent != null && intent.isDocument(); diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index fea52f2f3b8d..6d96cf06a00b 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -25,6 +25,8 @@ import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS; +import static com.android.server.wm.ActivityRecord.State.STOPPED; +import static com.android.server.wm.ActivityRecord.State.STOPPING; import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION; @@ -123,6 +125,11 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Updated config=%s", targetActivity.getConfiguration()); } + } else if (mDefaultTaskDisplayArea.getActivity( + ActivityRecord::occludesParent, false /* traverseTopToBottom */) == null) { + // Skip because none of above activities can occlude the target activity. The preload + // should be done silently in background without being visible. + return; } else { // Create the activity record. Because the activity is invisible, this doesn't really // start the client. @@ -149,8 +156,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan // Invisible activity should be stopped. If the recents activity is alive and its doesn't // need to relaunch by current configuration, then it may be already in stopped state. - if (!targetActivity.isState(Task.ActivityState.STOPPING, - Task.ActivityState.STOPPED)) { + if (!targetActivity.isState(STOPPING, STOPPED)) { // Add to stopping instead of stop immediately. So the client has the chance to perform // traversal in non-stopped state (ViewRootImpl.mStopped) that would initialize more // things (e.g. the measure can be done earlier). The actual stop will be performed when @@ -467,7 +473,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan */ static void notifyAnimationCancelBeforeStart(IRecentsAnimationRunner recentsAnimationRunner) { try { - recentsAnimationRunner.onAnimationCanceled(null /* taskSnapshot */); + recentsAnimationRunner.onAnimationCanceled(null /* taskIds */, + null /* taskSnapshots */); } catch (RemoteException e) { Slog.e(TAG, "Failed to cancel recents animation before start", e); } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index e346e3ec7db9..5dd8ef39e8e7 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; @@ -123,6 +124,7 @@ public class RecentsAnimationController implements DeathRecipient { private final int mDisplayId; private boolean mWillFinishToHome = false; private final Runnable mFailsafeRunnable = this::onFailsafe; + private Runnable mCheckRotationAfterCleanup; // The recents component app token that is shown behind the visibile tasks private ActivityRecord mTargetActivityRecord; @@ -162,14 +164,17 @@ public class RecentsAnimationController implements DeathRecipient { private boolean mNavigationBarAttachedToApp; private ActivityRecord mNavBarAttachedApp; + private final ArrayList<RemoteAnimationTarget> mPendingTaskAppears = new ArrayList<>(); + /** * An app transition listener to cancel the recents animation only after the app transition * starts or is canceled. */ final AppTransitionListener mAppTransitionListener = new AppTransitionListener() { @Override - public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration, - long statusBarAnimationStartTime, long statusBarAnimationDuration) { + public int onAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration, long statusBarAnimationStartTime, + long statusBarAnimationDuration) { continueDeferredCancel(); return 0; } @@ -259,13 +264,6 @@ public class RecentsAnimationController implements DeathRecipient { "finish(%b): mCanceled=%b", moveHomeToTop, mCanceled); final long token = Binder.clearCallingIdentity(); try { - synchronized (mService.getWindowManagerLock()) { - // Remove all new task targets. - for (int i = mPendingNewTaskTargets.size() - 1; i >= 0; i--) { - removeTaskInternal(mPendingNewTaskTargets.get(i)); - } - } - // Note, the callback will handle its own synchronization, do not lock on WM lock // prior to calling the callback mCallbacks.onAnimationFinished(moveHomeToTop @@ -581,7 +579,7 @@ public class RecentsAnimationController implements DeathRecipient { contentInsets = targetAppMainWindow .getInsetsStateWithVisibilityOverride() .calculateInsets(mTargetActivityRecord.getBounds(), Type.systemBars(), - false /* ignoreVisibility */); + false /* ignoreVisibility */).toRect(); } else { // If the window for the activity had not yet been created, use the display insets. mService.getStableInsets(mDisplayId, mTmpRect); @@ -730,11 +728,19 @@ public class RecentsAnimationController implements DeathRecipient { return; } ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addTaskToTargets, target: %s", target); - try { - mRunner.onTaskAppeared(target); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to report task appeared", e); - } + mPendingTaskAppears.add(target); + } + } + + void sendTasksAppeared() { + if (mPendingTaskAppears.isEmpty() || mRunner == null) return; + try { + final RemoteAnimationTarget[] targets = mPendingTaskAppears.toArray( + new RemoteAnimationTarget[0]); + mRunner.onTasksAppeared(targets); + mPendingTaskAppears.clear(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to report task appeared", e); } } @@ -742,10 +748,15 @@ public class RecentsAnimationController implements DeathRecipient { OnAnimationFinishedCallback finishedCallback) { final SparseBooleanArray recentTaskIds = mService.mAtmService.getRecentTasks().getRecentTaskIds(); - TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task, - !recentTaskIds.get(task.mTaskId), true /* hidden */, finishedCallback); - mPendingNewTaskTargets.add(task.mTaskId); - return adapter.createRemoteAnimationTarget(); + // The target must be built off the root task (the leaf task surface would be cropped + // within the root surface). However, recents only tracks leaf task ids, so we'll replace + // the task-id with the leaf id. + final Task leafTask = task.getTopLeafTask(); + int taskId = leafTask.mTaskId; + TaskAnimationAdapter adapter = addAnimation(task, + !recentTaskIds.get(taskId), true /* hidden */, finishedCallback); + mPendingNewTaskTargets.add(taskId); + return adapter.createRemoteAnimationTarget(taskId); } void logRecentsAnimationStartTime(int durationMs) { @@ -780,7 +791,8 @@ public class RecentsAnimationController implements DeathRecipient { final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i); - final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationTarget(); + final RemoteAnimationTarget target = + taskAdapter.createRemoteAnimationTarget(INVALID_TASK_ID); if (target != null) { targets.add(target); } else { @@ -792,7 +804,7 @@ public class RecentsAnimationController implements DeathRecipient { private RemoteAnimationTarget[] createWallpaperAnimations() { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "createWallpaperAnimations()"); - return WallpaperAnimationAdapter.startWallpaperAnimations(mService, 0L, 0L, + return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent, 0L, 0L, adapter -> { synchronized (mService.mGlobalLock) { // If the wallpaper animation is canceled, continue with the recents @@ -851,37 +863,37 @@ public class RecentsAnimationController implements DeathRecipient { mCanceled = true; if (screenshot && !mPendingAnimations.isEmpty()) { - final TaskAnimationAdapter adapter = mPendingAnimations.get(0); - final Task task = adapter.mTask; - // Screen shot previous task when next task starts transition and notify the runner. - // We will actually finish the animation once the runner calls cleanUpScreenshot(). - final TaskSnapshot taskSnapshot = screenshotRecentTask(task); + final ArrayMap<Task, TaskSnapshot> snapshotMap = screenshotRecentTasks(); mPendingCancelWithScreenshotReorderMode = reorderMode; - try { - mRunner.onAnimationCanceled(taskSnapshot); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to cancel recents animation", e); - } - if (taskSnapshot != null) { - // Defer until the runner calls back to cleanupScreenshot() - adapter.setSnapshotOverlay(taskSnapshot); + + if (!snapshotMap.isEmpty()) { + try { + int[] taskIds = new int[snapshotMap.size()]; + TaskSnapshot[] snapshots = new TaskSnapshot[snapshotMap.size()]; + for (int i = snapshotMap.size() - 1; i >= 0; i--) { + taskIds[i] = snapshotMap.keyAt(i).mTaskId; + snapshots[i] = snapshotMap.valueAt(i); + } + mRunner.onAnimationCanceled(taskIds, snapshots); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to cancel recents animation", e); + } // Schedule a new failsafe for if the runner doesn't clean up the screenshot scheduleFailsafe(); - } else { - // Do a normal cancel since we couldn't screenshot - mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); + return; } - } else { - // Otherwise, notify the runner and clean up the animation immediately - // Note: In the fallback case, this can trigger multiple onAnimationCancel() calls - // to the runner if we this actually triggers cancel twice on the caller - try { - mRunner.onAnimationCanceled(null /* taskSnapshot */); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to cancel recents animation", e); - } - mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); + // Fallback to a normal cancel since we couldn't screenshot } + + // Notify the runner and clean up the animation immediately + // Note: In the fallback case, this can trigger multiple onAnimationCancel() calls + // to the runner if we this actually triggers cancel twice on the caller + try { + mRunner.onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to cancel recents animation", e); + } + mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); } } @@ -921,6 +933,24 @@ public class RecentsAnimationController implements DeathRecipient { } /** + * If the display rotation change is ignored while recents animation is running, make sure that + * the pending rotation change will be applied after the animation finishes. + */ + void setCheckRotationAfterCleanup() { + if (mCheckRotationAfterCleanup != null) return; + mCheckRotationAfterCleanup = () -> { + synchronized (mService.mGlobalLock) { + if (mDisplayContent.getDisplayRotation() + .updateRotationAndSendNewConfigIfChanged()) { + if (mTargetActivityRecord != null) { + mTargetActivityRecord.finishFixedRotationTransform(); + } + } + } + }; + } + + /** * @return Whether we should defer the cancel from a root task order change until the next app * transition. */ @@ -937,13 +967,23 @@ public class RecentsAnimationController implements DeathRecipient { return mRequestDeferCancelUntilNextTransition && mCancelDeferredWithScreenshot; } - TaskSnapshot screenshotRecentTask(Task task) { + private ArrayMap<Task, TaskSnapshot> screenshotRecentTasks() { final TaskSnapshotController snapshotController = mService.mTaskSnapshotController; - final ArraySet<Task> tasks = Sets.newArraySet(task); - snapshotController.snapshotTasks(tasks); - snapshotController.addSkipClosingAppSnapshotTasks(tasks); - return snapshotController.getSnapshot(task.mTaskId, task.mUserId, - false /* restoreFromDisk */, false /* isLowResolution */); + final ArrayMap<Task, TaskSnapshot> snapshotMap = new ArrayMap<>(); + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + final TaskAnimationAdapter adapter = mPendingAnimations.get(i); + final Task task = adapter.mTask; + snapshotController.recordTaskSnapshot(task, false /* allowSnapshotHome */); + final TaskSnapshot snapshot = snapshotController.getSnapshot(task.mTaskId, task.mUserId, + false /* restoreFromDisk */, false /* isLowResolution */); + if (snapshot != null) { + snapshotMap.put(task, snapshot); + // Defer until the runner calls back to cleanupScreenshot() + adapter.setSnapshotOverlay(snapshot); + } + } + snapshotController.addSkipClosingAppSnapshotTasks(snapshotMap.keySet()); + return snapshotMap; } void cleanupAnimation(@ReorderMode int reorderMode) { @@ -965,6 +1005,9 @@ public class RecentsAnimationController implements DeathRecipient { removeAnimation(taskAdapter); taskAdapter.onCleanup(); } + // Should already be empty, but clean-up pending task-appears in-case they weren't sent. + mPendingNewTaskTargets.clear(); + mPendingTaskAppears.clear(); for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) { final WallpaperAnimationAdapter wallpaperAdapter = mPendingWallpaperAnimations.get(i); @@ -1007,6 +1050,10 @@ public class RecentsAnimationController implements DeathRecipient { if (mStatusBar != null) { mStatusBar.onRecentsAnimationStateChanged(false /* running */); } + if (mCheckRotationAfterCleanup != null) { + mService.mH.post(mCheckRotationAfterCleanup); + mCheckRotationAfterCleanup = null; + } } void scheduleFailsafe() { @@ -1102,6 +1149,13 @@ public class RecentsAnimationController implements DeathRecipient { return mTargetActivityRecord.findMainWindow(); } + DisplayArea getTargetAppDisplayArea() { + if (mTargetActivityRecord == null) { + return null; + } + return mTargetActivityRecord.getDisplayArea(); + } + boolean isAnimatingTask(Task task) { for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { if (task == mPendingAnimations.get(i).mTask) { @@ -1183,7 +1237,14 @@ public class RecentsAnimationController implements DeathRecipient { mLocalBounds.offsetTo(tmpPos.x, tmpPos.y); } - RemoteAnimationTarget createRemoteAnimationTarget() { + /** + * @param overrideTaskId overrides the target's taskId. It may differ from mTaskId and thus + * can differ from taskInfo. This mismatch is needed, however, in + * some cases where we are animating root tasks but need need leaf + * ids for identification. If this is INVALID (-1), then mTaskId + * will be used. + */ + RemoteAnimationTarget createRemoteAnimationTarget(int overrideTaskId) { final ActivityRecord topApp = mTask.getTopVisibleActivity(); final WindowState mainWindow = topApp != null ? topApp.findMainWindow() @@ -1192,16 +1253,20 @@ public class RecentsAnimationController implements DeathRecipient { return null; } final Rect insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets( - mBounds, Type.systemBars(), false /* ignoreVisibility */); + mBounds, Type.systemBars(), false /* ignoreVisibility */).toRect(); InsetUtils.addInsets(insets, mainWindow.mActivityRecord.getLetterboxInsets()); final int mode = topApp.getActivityType() == mTargetActivityType ? MODE_OPENING : MODE_CLOSING; - mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash, + if (overrideTaskId < 0) { + overrideTaskId = mTask.mTaskId; + } + mTarget = new RemoteAnimationTarget(overrideTaskId, mode, mCapturedLeash, !topApp.fillsParent(), new Rect(), insets, mTask.getPrefixOrderIndex(), new Point(mBounds.left, mBounds.top), mLocalBounds, mBounds, mTask.getWindowConfiguration(), - mIsRecentTaskInvisible, null, null, mTask.getTaskInfo()); + mIsRecentTaskInvisible, null, null, mTask.getTaskInfo(), + topApp.checkEnterPictureInPictureAppOpsState()); return mTarget; } @@ -1289,7 +1354,7 @@ public class RecentsAnimationController implements DeathRecipient { @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, - @AnimationType int type, OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) { // Restore position and root task crop until client has a chance to modify it. t.setPosition(animationLeash, mLocalBounds.left, mLocalBounds.top); mTmpRect.set(mLocalBounds); diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 1a429f88fe2e..eeac230489f9 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -22,6 +22,7 @@ import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import android.annotation.NonNull; import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; @@ -65,7 +66,6 @@ class RemoteAnimationController implements DeathRecipient { new ArrayList<>(); @VisibleForTesting final ArrayList<NonAppWindowAnimationAdapter> mPendingNonAppAnimations = new ArrayList<>(); - private final Rect mTmpRect = new Rect(); private final Handler mHandler; private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable"); @@ -85,18 +85,18 @@ class RemoteAnimationController implements DeathRecipient { * Creates an animation record for each individual {@link WindowContainer}. * * @param windowContainer The windows to animate. - * @param position The position app bounds, in screen coordinates. + * @param position The position app bounds relative to its parent. * @param localBounds The bounds of the app relative to its parent. - * @param stackBounds The stack bounds of the app relative to position. - * @param startBounds The stack bounds before the transition, in screen coordinates + * @param endBounds The end bounds after the transition, in screen coordinates. + * @param startBounds The start bounds before the transition, in screen coordinates. * @return The record representing animation(s) to run on the app. */ RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer, - Point position, Rect localBounds, Rect stackBounds, Rect startBounds) { + Point position, Rect localBounds, Rect endBounds, Rect startBounds) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s", windowContainer); final RemoteAnimationRecord adapters = new RemoteAnimationRecord(windowContainer, position, - localBounds, stackBounds, startBounds); + localBounds, endBounds, startBounds); mPendingAnimations.add(adapters); return adapters; } @@ -208,7 +208,7 @@ class RemoteAnimationController implements DeathRecipient { if (wrappers.mThumbnailAdapter != null && wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) { wrappers.mThumbnailAdapter.mCapturedFinishCallback - .onAnimationFinished(wrappers.mAdapter.mAnimationType, + .onAnimationFinished(wrappers.mThumbnailAdapter.mAnimationType, wrappers.mThumbnailAdapter); } mPendingAnimations.remove(i); @@ -219,7 +219,7 @@ class RemoteAnimationController implements DeathRecipient { private RemoteAnimationTarget[] createWallpaperAnimations() { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createWallpaperAnimations()"); - return WallpaperAnimationAdapter.startWallpaperAnimations(mService, + return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent, mRemoteAnimationAdapter.getDuration(), mRemoteAnimationAdapter.getStatusBarTransitionDelay(), adapter -> { @@ -261,7 +261,7 @@ class RemoteAnimationController implements DeathRecipient { } if (adapters.mThumbnailAdapter != null) { adapters.mThumbnailAdapter.mCapturedFinishCallback - .onAnimationFinished(adapters.mAdapter.mAnimationType, + .onAnimationFinished(adapters.mThumbnailAdapter.mAnimationType, adapters.mThumbnailAdapter); } mPendingAnimations.remove(i); @@ -396,6 +396,7 @@ class RemoteAnimationController implements DeathRecipient { RemoteAnimationTarget mTarget; final WindowContainer mWindowContainer; final Rect mStartBounds; + private @RemoteAnimationTarget.Mode int mMode = RemoteAnimationTarget.MODE_CHANGING; RemoteAnimationRecord(WindowContainer windowContainer, Point endPos, Rect localBounds, Rect endBounds, Rect startBounds) { @@ -404,16 +405,17 @@ class RemoteAnimationController implements DeathRecipient { mStartBounds = new Rect(startBounds); mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds, mStartBounds); - mTmpRect.set(startBounds); - mTmpRect.offsetTo(0, 0); if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) { - mThumbnailAdapter = - new RemoteAnimationAdapterWrapper(this, new Point(0, 0), localBounds, - mTmpRect, new Rect()); + final Rect thumbnailLocalBounds = new Rect(startBounds); + thumbnailLocalBounds.offsetTo(0, 0); + // Snapshot is located at (0,0) of the animation leash. It doesn't have size + // change, so the startBounds is its end bounds, and no start bounds for it. + mThumbnailAdapter = new RemoteAnimationAdapterWrapper(this, new Point(0, 0), + thumbnailLocalBounds, startBounds, new Rect()); } } else { mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds, - new Rect(endPos.x, endPos.y, endBounds.right, endBounds.bottom)); + new Rect()); mStartBounds = null; } } @@ -428,18 +430,25 @@ class RemoteAnimationController implements DeathRecipient { return mTarget; } + void setMode(@RemoteAnimationTarget.Mode int mode) { + mMode = mode; + } + int getMode() { - final DisplayContent dc = mWindowContainer.getDisplayContent(); - final ActivityRecord topActivity = mWindowContainer.getTopMostActivity(); - // Note that opening/closing transitions are per-activity while changing transitions - // are per-task. - if (dc.mOpeningApps.contains(topActivity)) { - return RemoteAnimationTarget.MODE_OPENING; - } else if (dc.mChangingContainers.contains(mWindowContainer)) { - return RemoteAnimationTarget.MODE_CHANGING; - } else { - return RemoteAnimationTarget.MODE_CLOSING; + return mMode; + } + + /** Whether its parent is also an animation target in the same transition. */ + boolean hasAnimatingParent() { + // mOpeningApps and mClosingApps are only activities, so only need to check + // mChangingContainers. + for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) { + if (mWindowContainer.isDescendantOf( + mDisplayContent.mChangingContainers.valueAt(i))) { + return true; + } } + return false; } } @@ -450,15 +459,15 @@ class RemoteAnimationController implements DeathRecipient { private @AnimationType int mAnimationType; final Point mPosition = new Point(); final Rect mLocalBounds; - final Rect mRootTaskBounds = new Rect(); + final Rect mEndBounds = new Rect(); final Rect mStartBounds = new Rect(); RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position, - Rect localBounds, Rect rootTaskBounds, Rect startBounds) { + Rect localBounds, Rect endBounds, Rect startBounds) { mRecord = record; mPosition.set(position.x, position.y); mLocalBounds = localBounds; - mRootTaskBounds.set(rootTaskBounds); + mEndBounds.set(endBounds); mStartBounds.set(startBounds); } @@ -469,15 +478,20 @@ class RemoteAnimationController implements DeathRecipient { @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, - @AnimationType int type, OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation"); - // Restore position and stack crop until client has a chance to modify it. if (mStartBounds.isEmpty()) { - t.setPosition(animationLeash, 0, 0); - t.setWindowCrop(animationLeash, -1, -1); + // Restore position and stack crop until client has a chance to modify it. + t.setPosition(animationLeash, mPosition.x, mPosition.y); + t.setWindowCrop(animationLeash, mEndBounds.width(), mEndBounds.height()); } else { - t.setPosition(animationLeash, mStartBounds.left, mStartBounds.top); + // Offset the change animation leash to the relative start position in parent. + // (mPosition) is the relative end position in parent container. + // (mStartBounds - mEndBounds) is the position difference between start and end. + // (mPosition + mStartBounds - mEndBounds) will be the relative start position. + t.setPosition(animationLeash, mPosition.x + mStartBounds.left - mEndBounds.left, + mPosition.y + mStartBounds.top - mEndBounds.top); t.setWindowCrop(animationLeash, mStartBounds.width(), mStartBounds.height()); } mCapturedLeash = animationLeash; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 6844656b6e09..99f9978ce9fb 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -37,10 +37,10 @@ import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE; import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; -import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; import static android.view.WindowManager.TRANSIT_NONE; +import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; @@ -48,11 +48,18 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_O import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.wm.ActivityRecord.State.DESTROYED; +import static com.android.server.wm.ActivityRecord.State.FINISHING; +import static com.android.server.wm.ActivityRecord.State.PAUSED; +import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.ActivityRecord.State.STOPPED; +import static com.android.server.wm.ActivityRecord.State.STOPPING; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; @@ -67,20 +74,14 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList; import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity; import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG; +import static com.android.server.wm.RootWindowContainerProto.DEFAULT_MIN_SIZE_RESIZABLE_TASK; import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT; import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER; import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER; -import static com.android.server.wm.Task.ActivityState.FINISHING; -import static com.android.server.wm.Task.ActivityState.PAUSED; -import static com.android.server.wm.Task.ActivityState.RESUMED; -import static com.android.server.wm.Task.ActivityState.STOPPED; -import static com.android.server.wm.Task.ActivityState.STOPPING; -import static com.android.server.wm.Task.ActivityState.DESTROYED; import static com.android.server.wm.Task.REPARENT_LEAVE_ROOT_TASK_IN_PLACE; import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT; -import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE; +import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -373,8 +374,26 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> return false; } + if (matchingCandidate(task)) { + return true; + } + + // Looking for the embedded tasks (if any) + return !task.isLeafTaskFragment() && task.forAllLeafTaskFragments( + this::matchingCandidate); + } + + boolean matchingCandidate(TaskFragment taskFragment) { + final Task task = taskFragment.asTask(); + if (task == null) { + return false; + } + // Overlays should not be considered as the task's logical top activity. - final ActivityRecord r = task.getTopNonFinishingActivity(false /* includeOverlays */); + // Activities of the tasks that embedded from this one should not be used. + final ActivityRecord r = task.getTopNonFinishingActivity(false /* includeOverlays */, + false /* includingEmbeddedTask */); + if (r == null || r.finishing || r.mUserId != userId || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { ProtoLog.d(WM_DEBUG_TASKS, "Skipping %s: mismatch root %s", task, r); @@ -494,6 +513,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> mTopFocusedDisplayId = topFocusedDisplayId; mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId); mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId); + mWmService.mAccessibilityController.setFocusedDisplay(topFocusedDisplayId); ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "New topFocusedDisplayId=%d", topFocusedDisplayId); } return changed; @@ -517,6 +537,11 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> mTaskSupervisor.updateTopResumedActivityIfNeeded(); } + @Override + boolean isAttached() { + return true; + } + /** * Called when DisplayWindowSettings values may change. */ @@ -825,8 +850,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> } // Initialize state of exiting tokens. - final int numDisplays = mChildren.size(); - for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) { final DisplayContent displayContent = mChildren.get(displayNdx); displayContent.setExitingTokensHasVisible(false); } @@ -863,6 +887,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> // Send any pending task-info changes that were queued-up during a layout deferment mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); + mWmService.mAtmService.mTaskFragmentOrganizerController.dispatchPendingEvents(); mWmService.mSyncEngine.onSurfacePlacement(); mWmService.mAnimator.executeAfterPrepareSurfacesRunnables(); @@ -875,10 +900,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> recentsAnimationController.checkAnimationReady(defaultDisplay.mWallpaperController); } - for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) { final DisplayContent displayContent = mChildren.get(displayNdx); if (displayContent.mWallpaperMayChange) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Wallpaper may change! Adjusting"); + ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper may change! Adjusting"); displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; if (DEBUG_LAYOUT_REPEATS) { surfacePlacer.debugLayoutRepeats("WallpaperMayChange", @@ -937,12 +962,12 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> } // Time to remove any exiting tokens? - for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + for (int displayNdx = mChildren.size() - 1; displayNdx >= 0; --displayNdx) { final DisplayContent displayContent = mChildren.get(displayNdx); displayContent.removeExistingTokensIfPossible(); } - for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) { final DisplayContent displayContent = mChildren.get(displayNdx); if (displayContent.pendingLayoutChanges != 0) { displayContent.setLayoutNeeded(); @@ -1238,6 +1263,11 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> pw.println(mTopFocusedDisplayId); } + void dumpDefaultMinSizeOfResizableTask(PrintWriter pw) { + pw.print(" mDefaultMinSizeOfResizeableTaskDp="); + pw.println(mDefaultMinSizeOfResizeableTaskDp); + } + void dumpLayoutNeededDisplayIds(PrintWriter pw) { if (!isLayoutNeeded()) { return; @@ -1284,7 +1314,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> mTaskSupervisor.getKeyguardController().dumpDebug(proto, KEYGUARD_CONTROLLER); proto.write(IS_HOME_RECENTS_COMPONENT, mTaskSupervisor.mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser)); - + proto.write(DEFAULT_MIN_SIZE_RESIZABLE_TASK, mDefaultMinSizeOfResizeableTaskDp); proto.end(token); } @@ -1873,7 +1903,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> if (focusedRootTask == null) { return null; } - final ActivityRecord resumedActivity = focusedRootTask.getResumedActivity(); + final ActivityRecord resumedActivity = focusedRootTask.getTopResumedActivity(); if (resumedActivity != null && resumedActivity.app != null) { return resumedActivity; } @@ -1895,11 +1925,11 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> // foreground. WindowProcessController fgApp = getItemFromRootTasks(rootTask -> { if (isTopDisplayFocusedRootTask(rootTask)) { - final ActivityRecord resumedActivity = rootTask.getResumedActivity(); + final ActivityRecord resumedActivity = rootTask.getTopResumedActivity(); if (resumedActivity != null) { return resumedActivity.app; - } else if (rootTask.getPausingActivity() != null) { - return rootTask.getPausingActivity().app; + } else if (rootTask.getTopPausingActivity() != null) { + return rootTask.getTopPausingActivity().app; } } return null; @@ -1925,7 +1955,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> return; } - if (rootTask.getVisibility(null /*starting*/) == TASK_VISIBILITY_INVISIBLE) { + if (rootTask.getVisibility(null /* starting */) + == TASK_FRAGMENT_VISIBILITY_INVISIBLE) { return; } @@ -1949,7 +1980,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> private boolean startActivityForAttachedApplicationIfNeeded(ActivityRecord r, WindowProcessController app, ActivityRecord top) { - if (r.finishing || !r.okToShowLocked() || !r.visibleIgnoringKeyguard || r.app != null + if (r.finishing || !r.showToCurrentUser() || !r.visibleIgnoringKeyguard || r.app != null || app.mUid != r.info.applicationInfo.uid || !app.mName.equals(r.processName)) { return false; } @@ -1982,7 +2013,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> */ void ensureActivitiesVisible(ActivityRecord starting, int configChanges, boolean preserveWindows, boolean notifyClients) { - if (mTaskSupervisor.inActivityVisibilityUpdate()) { + if (mTaskSupervisor.inActivityVisibilityUpdate() + || mTaskSupervisor.isRootVisibilityUpdateDeferred()) { // Don't do recursive work. return; } @@ -2197,7 +2229,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> // display area, so reparent. rootTask.reparent(taskDisplayArea, true /* onTop */); } - mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_CHANGE, rootTask); + rootTask.mTransitionController.requestTransitionIfNeeded(TRANSIT_PIP, rootTask); // Defer the windowing mode change until after the transition to prevent the activity // from doing work and changing the activity visuals while animating @@ -2502,7 +2534,9 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> if (displayShouldSleep) { rootTask.goToSleepIfPossible(false /* shuttingDown */); } else { - rootTask.awakeFromSleepingLocked(); + rootTask.forAllLeafTasksAndLeafTaskFragments( + taskFragment -> taskFragment.awakeFromSleeping(), + true /* traverseTopToBottom */); if (rootTask.isFocusedRootTaskOnDisplay() && !mTaskSupervisor.getKeyguardController() .isKeyguardOrAodShowing(display.mDisplayId)) { @@ -2768,6 +2802,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> token = new SleepToken(tag, displayId); mSleepTokens.put(tokenKey, token); display.mAllSleepTokens.add(token); + ProtoLog.d(WM_DEBUG_STATES, "Create sleep token: tag=%s, displayId=%d", tag, displayId); } else { throw new RuntimeException("Create the same sleep token twice: " + token); } @@ -2786,6 +2821,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> return; } + ProtoLog.d(WM_DEBUG_STATES, "Remove sleep token: tag=%s, displayId=%d", token.mTag, + token.mDisplayId); display.mAllSleepTokens.remove(token); if (display.mAllSleepTokens.isEmpty()) { mService.updateSleepIfNeededLocked(); @@ -2879,8 +2916,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> if (DEBUG_SWITCH) { Slog.v(TAG_SWITCH, "Destroying " + r + " in state " + r.getState() - + " resumed=" + r.getTask().getResumedActivity() + " pausing=" - + r.getTask().getPausingActivity() + " for reason " + + " resumed=" + r.getTask().getTopResumedActivity() + " pausing=" + + r.getTask().getTopPausingActivity() + " for reason " + mDestroyAllActivitiesReason); } @@ -2914,7 +2951,6 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); r.detachFromProcess(); - r.mDisplayContent.prepareAppTransition(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED); r.mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED); r.destroyIfPossible("handleAppCrashed"); @@ -3467,7 +3503,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> if (rootTask == null || !rootTask.hasActivity()) { continue; } - final ActivityRecord resumedActivity = rootTask.getResumedActivity(); + final ActivityRecord resumedActivity = rootTask.getTopResumedActivity(); if (resumedActivity == null || !resumedActivity.idle) { ProtoLog.d(WM_DEBUG_STATES, "allResumedActivitiesIdle: rootTask=%d %s " + "not idle", rootTask.getRootTaskId(), resumedActivity); @@ -3482,7 +3518,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> boolean allResumedActivitiesVisible() { boolean[] foundResumed = {false}; final boolean foundInvisibleResumedActivity = forAllRootTasks(rootTask -> { - final ActivityRecord r = rootTask.getResumedActivity(); + final ActivityRecord r = rootTask.getTopResumedActivity(); if (r != null) { if (!r.nowVisible) { return true; @@ -3500,7 +3536,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> boolean allPausedActivitiesComplete() { boolean[] pausing = {true}; final boolean hasActivityNotCompleted = forAllLeafTasks(task -> { - final ActivityRecord r = task.getPausingActivity(); + final ActivityRecord r = task.getTopPausingActivity(); if (r != null && !r.isState(PAUSED, STOPPED, STOPPING, FINISHING)) { ProtoLog.d(WM_DEBUG_STATES, "allPausedActivitiesComplete: " + "r=%s state=%s", r, r.getState()); @@ -3543,14 +3579,6 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> }, true /* traverseTopToBottom */); } - void cancelInitializingActivities() { - forAllRootTasks(task -> { - // We don't want to clear starting window for activities that aren't occluded - // as we need to display their starting window until they are done initializing. - task.forAllOccludedActivities(ActivityRecord::cancelInitializing); - }); - } - Task anyTaskForId(int id) { return anyTaskForId(id, MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE); } @@ -3630,11 +3658,6 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> return task; } - ActivityRecord isInAnyTask(IBinder token) { - final ActivityRecord r = ActivityRecord.forTokenLocked(token); - return (r != null && r.isDescendantOf(this)) ? r : null; - } - @VisibleForTesting void getRunningTasks(int maxNum, List<ActivityManager.RunningTaskInfo> list, int flags, int callingUid, ArraySet<Integer> profileIds) { @@ -3643,14 +3666,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> } void startPowerModeLaunchIfNeeded(boolean forceSend, ActivityRecord targetActivity) { - final boolean sendPowerModeLaunch; - - if (forceSend) { - sendPowerModeLaunch = true; - } else if (targetActivity == null || targetActivity.app == null) { - // Set power mode if we don't know what we're launching yet. - sendPowerModeLaunch = true; - } else { + if (!forceSend && targetActivity != null && targetActivity.app != null) { // Set power mode when the activity's process is different than the current top resumed // activity on all display areas, or if there are no resumed activities in the system. boolean[] noResumedActivities = {true}; @@ -3666,15 +3682,32 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> !resumedActivityProcess.equals(targetActivity.app); } }); - sendPowerModeLaunch = noResumedActivities[0] || allFocusedProcessesDiffer[0]; + if (!noResumedActivities[0] && !allFocusedProcessesDiffer[0]) { + // All focused activities are resumed and the process of the target activity is + // the same as them, e.g. delivering new intent to the current top. + return; + } } - if (sendPowerModeLaunch) { - mService.startLaunchPowerMode( - ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); + int reason = ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY; + // If the activity is launching while keyguard is locked (including occluded), the activity + // may be visible until its first relayout is done (e.g. apply show-when-lock flag). To + // avoid power mode from being cleared before that, add a special reason to consider whether + // the unknown visibility is resolved. The case from SystemUI is excluded because it should + // rely on keyguard-going-away. + if (mService.mKeyguardController.isKeyguardLocked() && targetActivity != null + && !targetActivity.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_SYSTEMUI)) { + final ActivityOptions opts = targetActivity.getOptions(); + if (opts == null || opts.getSourceInfo() == null + || opts.getSourceInfo().type != ActivityOptions.SourceInfo.TYPE_LOCKSCREEN) { + reason |= ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY; + } } + mService.startLaunchPowerMode(reason); } + // TODO(b/191434136): handle this properly when we add multi-window support on secondary + // display. private void calculateDefaultMinimalSizeOfResizeableTasks() { final Resources res = mService.mContext.getResources(); final float minimalSize = res.getDimension( @@ -3801,6 +3834,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent> return "{\"" + mTag + "\", display " + mDisplayId + ", acquire at " + TimeUtils.formatUptime(mAcquireTime) + "}"; } + + void writeTagToProto(ProtoOutputStream proto, long fieldId) { + proto.write(fieldId, mTag); + } } private class RankTaskLayersRunnable implements Runnable { diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java index 7ba772c18455..9864297de529 100644 --- a/services/core/java/com/android/server/wm/RunningTasks.java +++ b/services/core/java/com/android/server/wm/RunningTasks.java @@ -54,6 +54,7 @@ class RunningTasks { private boolean mAllowed; private boolean mFilterOnlyVisibleRecents; private Task mTopDisplayFocusRootTask; + private Task mTopDisplayAdjacentTask; private RecentTasks mRecentTasks; private boolean mKeepIntentExtra; @@ -77,6 +78,12 @@ class RunningTasks { mRecentTasks = root.mService.getRecentTasks(); mKeepIntentExtra = (flags & FLAG_KEEP_INTENT_EXTRA) == FLAG_KEEP_INTENT_EXTRA; + if (mTopDisplayFocusRootTask.getAdjacentTaskFragment() != null) { + mTopDisplayAdjacentTask = mTopDisplayFocusRootTask.getAdjacentTaskFragment().asTask(); + } else { + mTopDisplayAdjacentTask = null; + } + final PooledConsumer c = PooledLambda.obtainConsumer(RunningTasks::processTask, this, PooledLambda.__(Task.class)); root.forAllLeafTasks(c, false); @@ -126,6 +133,12 @@ class RunningTasks { // can be used to determine the order of the tasks (it may not be set for newly // created tasks) task.touchActiveTime(); + } else if (rootTask == mTopDisplayAdjacentTask && rootTask.getTopMostTask() == task) { + // The short-term workaround for launcher could get suitable running task info in + // split screen. + task.touchActiveTime(); + // TreeSet doesn't allow same value and make sure this task is lower than focus one. + task.lastActiveTime--; } mTmpSortedSet.add(task); diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index 4892005631ba..2d4aef682d62 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -78,6 +78,18 @@ public class SafeActivityOptions { } /** + * Constructs a new instance from a bundle and provided pid/uid. + * + * @param bOptions The {@link ActivityOptions} as {@link Bundle}. + */ + static SafeActivityOptions fromBundle(Bundle bOptions, int callingPid, int callingUid) { + return bOptions != null + ? new SafeActivityOptions(ActivityOptions.fromBundle(bOptions), + callingPid, callingUid) + : null; + } + + /** * Constructs a new instance and records {@link Binder#getCallingPid}/ * {@link Binder#getCallingUid}. Thus, calling identity MUST NOT be cleared when constructing * this object. @@ -91,6 +103,17 @@ public class SafeActivityOptions { } /** + * Constructs a new instance. + * + * @param options The options to wrap. + */ + private SafeActivityOptions(@Nullable ActivityOptions options, int callingPid, int callingUid) { + mOriginalCallingPid = callingPid; + mOriginalCallingUid = callingUid; + mOriginalOptions = options; + } + + /** * Overrides options with options from a caller and records {@link Binder#getCallingPid}/ * {@link Binder#getCallingUid}. Thus, calling identity MUST NOT be cleared when calling this * method. diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index 1117191f8bf6..255905a5819d 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.util.RotationUtils.deltaRotation; +import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; @@ -30,8 +31,6 @@ import static com.android.server.wm.ScreenRotationAnimationProto.STARTED; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; -import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER; import android.animation.ArgbEvaluator; import android.content.Context; @@ -92,13 +91,6 @@ import java.io.PrintWriter; class ScreenRotationAnimation { private static final String TAG = TAG_WITH_CLASS_NAME ? "ScreenRotationAnimation" : TAG_WM; - /* - * Layers for screen rotation animation. We put these layers above - * WINDOW_FREEZE_LAYER so that screen freeze will cover all windows. - */ - private static final int SCREEN_FREEZE_LAYER_BASE = WINDOW_FREEZE_LAYER + TYPE_LAYER_MULTIPLIER; - private static final int SCREEN_FREEZE_LAYER_ENTER = SCREEN_FREEZE_LAYER_BASE; - private BoostFramework mPerf = null; private boolean mIsPerfLockAcquired = false; @@ -429,7 +421,7 @@ class ScreenRotationAnimation { finalWidth * 2, finalHeight * 2); Rect inner = new Rect(0, 0, finalWidth, finalHeight); mEnteringBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner, - SCREEN_FREEZE_LAYER_ENTER, mDisplayContent, false, mEnterBlackFrameLayer); + SCREEN_FREEZE_LAYER_BASE, mDisplayContent, false, mEnterBlackFrameLayer); } catch (OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate black surface", e); } @@ -752,7 +744,12 @@ class ScreenRotationAnimation { mScreenshotRotationAnimator = null; mRotateScreenAnimator = null; mService.mAnimator.mBulkUpdateParams |= WindowSurfacePlacer.SET_UPDATE_ROTATION; - kill(); + if (mDisplayContent.getRotationAnimation() == ScreenRotationAnimation.this) { + // It also invokes kill(). + mDisplayContent.setRotationAnimation(null); + } else { + kill(); + } mService.updateRotation(false, false); } } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index e28201245d9b..d8adc512b65a 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -69,6 +69,7 @@ import android.view.IWindowSessionCallback; import android.view.InputChannel; import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.WindowManager; @@ -113,7 +114,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { private float mLastReportedAnimatorScale; private String mPackageName; private String mRelayoutTag; - private final InsetsState mDummyRequestedVisibility = new InsetsState(); + private final InsetsVisibilities mDummyRequestedVisibilities = new InsetsVisibilities(); private final InsetsSourceControl[] mDummyControls = new InsetsSourceControl[0]; public Session(WindowManagerService service, IWindowSessionCallback callback) { @@ -187,29 +188,28 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { @Override public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs, - int viewVisibility, int displayId, InsetsState requestedVisibility, + int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { return mService.addWindow(this, window, attrs, viewVisibility, displayId, - UserHandle.getUserId(mUid), requestedVisibility, outInputChannel, outInsetsState, + UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState, outActiveControls); } - @Override public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs, - int viewVisibility, int displayId, int userId, InsetsState requestedVisibility, + int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId, - requestedVisibility, outInputChannel, outInsetsState, outActiveControls); + requestedVisibilities, outInputChannel, outInsetsState, outActiveControls); } @Override public int addToDisplayWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, InsetsState outInsetsState) { return mService.addWindow(this, window, attrs, viewVisibility, displayId, - UserHandle.getUserId(mUid), mDummyRequestedVisibility, null /* outInputChannel */, + UserHandle.getUserId(mUid), mDummyRequestedVisibilities, null /* outInputChannel */, outInsetsState, mDummyControls); } @@ -299,6 +299,17 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } } + + @Override + public boolean dropForAccessibility(IWindow window, int x, int y) { + final long ident = Binder.clearCallingIdentity(); + try { + return mDragDropController.dropForAccessibility(window, x, y); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + /** * Validates the given drag data. */ @@ -380,7 +391,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { final ShortcutServiceInternal shortcutService = LocalServices.getService(ShortcutServiceInternal.class); final Intent[] shortcutIntents = shortcutService.createShortcutIntents( - callingUid, callingPackage, packageName, shortcutId, + UserHandle.getUserId(callingUid), callingPackage, packageName, shortcutId, user.getIdentifier(), callingPid, callingUid); if (shortcutIntents == null || shortcutIntents.length == 0) { throw new IllegalArgumentException("Invalid shortcut id"); @@ -624,12 +635,12 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } @Override - public void insetsModified(IWindow window, InsetsState state) { + public void updateRequestedVisibilities(IWindow window, InsetsVisibilities visibilities) { synchronized (mService.mGlobalLock) { final WindowState windowState = mService.windowForClientLocked(this, window, false /* throwOnError */); if (windowState != null) { - windowState.updateRequestedVisibility(state); + windowState.setRequestedVisibilities(visibilities); windowState.getDisplayContent().getInsetsPolicy().onInsetsModified(windowState); } } diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java index be6a5d2fe27b..6ed59e96c700 100644 --- a/services/core/java/com/android/server/wm/ShellRoot.java +++ b/services/core/java/com/android/server/wm/ShellRoot.java @@ -197,7 +197,7 @@ public class ShellRoot { mAccessibilityWindow = null; } } - if (mDisplayContent.mWmService.mAccessibilityController != null) { + if (mDisplayContent.mWmService.mAccessibilityController.hasCallbacks()) { mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMoved( mDisplayContent.getDisplayId()); } diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java index c671e3835abc..8b1befbefd99 100644 --- a/services/core/java/com/android/server/wm/StartingData.java +++ b/services/core/java/com/android/server/wm/StartingData.java @@ -32,6 +32,12 @@ public abstract class StartingData { */ boolean mIsTransitionForward; + /** + * Non-null if the starting window should cover the bounds of associated task. It is assigned + * when the parent activity of starting window may be put in a partial area of the task. + */ + Task mAssociatedTask; + protected StartingData(WindowManagerService service, int typeParams) { mService = service; mTypeParams = typeParams; diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java index 48a7bdec2b94..cdf6b08b1c57 100644 --- a/services/core/java/com/android/server/wm/StrictModeFlash.java +++ b/services/core/java/com/android/server/wm/StrictModeFlash.java @@ -27,6 +27,7 @@ import android.graphics.Rect; import android.view.Surface; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl; +import android.view.WindowManagerPolicyConstants; class StrictModeFlash { private static final String TAG = TAG_WITH_CLASS_NAME ? "StrictModeFlash" : TAG_WM; @@ -52,7 +53,7 @@ class StrictModeFlash { .build(); // one more than Watermark? arbitrary. - t.setLayer(ctrl, WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); + t.setLayer(ctrl, WindowManagerPolicyConstants.STRICT_MODE_LAYER); t.setPosition(ctrl, 0, 0); t.show(ctrl); // Ensure we aren't considered as obscuring for Input purposes. diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index c7bf8ecfe949..50c9b31f425a 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -57,8 +57,11 @@ class SurfaceAnimator { @VisibleForTesting SurfaceControl mLeash; @VisibleForTesting + SurfaceFreezer.Snapshot mSnapshot; + @VisibleForTesting final Animatable mAnimatable; - private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback; + @VisibleForTesting + final OnAnimationFinishedCallback mInnerAnimationFinishedCallback; /** * Static callback to run on all animations started through this SurfaceAnimator @@ -151,12 +154,14 @@ class SurfaceAnimator { * @param animationFinishedCallback The callback being triggered when the animation finishes. * @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a * cancel call to the underlying AnimationAdapter. + * @param snapshotAnim The animation to run for the snapshot. {@code null} if there is no + * snapshot. */ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, @AnimationType int type, @Nullable OnAnimationFinishedCallback animationFinishedCallback, @Nullable Runnable animationCancelledCallback, - @Nullable SurfaceFreezer freezer) { + @Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) { cancelAnimation(t, true /* restarting */, true /* forwardCancel */); mAnimation = anim; mAnimationType = type; @@ -181,12 +186,20 @@ class SurfaceAnimator { return; } mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback); + if (snapshotAnim != null) { + mSnapshot = freezer.takeSnapshotForAnimation(); + if (mSnapshot == null) { + Slog.e(TAG, "No snapshot target to start animation on for " + mAnimatable); + return; + } + mSnapshot.startAnimation(t, snapshotAnim, type); + } } void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, @AnimationType int type) { startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */, - null /* animationCancelledCallback */, null /* freezer */); + null /* animationCancelledCallback */, null /* snapshotAnim */, null /* freezer */); } /** @@ -328,6 +341,7 @@ class SurfaceAnimator { final OnAnimationFinishedCallback animationFinishedCallback = mSurfaceAnimationFinishedCallback; final Runnable animationCancelledCallback = mAnimationCancelledCallback; + final SurfaceFreezer.Snapshot snapshot = mSnapshot; reset(t, false); if (animation != null) { if (!mAnimationStartDelayed && forwardCancel) { @@ -346,9 +360,14 @@ class SurfaceAnimator { } } - if (forwardCancel && leash != null) { - t.remove(leash); - mService.scheduleAnimationLocked(); + if (forwardCancel) { + if (snapshot != null) { + snapshot.cancelAnimation(t, false /* restarting */); + } + if (leash != null) { + t.remove(leash); + mService.scheduleAnimationLocked(); + } } if (!restarting) { @@ -361,6 +380,12 @@ class SurfaceAnimator { mAnimation = null; mSurfaceAnimationFinishedCallback = null; mAnimationType = ANIMATION_TYPE_NONE; + final SurfaceFreezer.Snapshot snapshot = mSnapshot; + mSnapshot = null; + if (snapshot != null) { + // Reset the mSnapshot reference before calling the callback to prevent circular reset. + snapshot.cancelAnimation(t, !destroyLeash); + } if (mLeash == null) { return; } @@ -377,11 +402,15 @@ class SurfaceAnimator { boolean scheduleAnim = false; final SurfaceControl surface = animatable.getSurfaceControl(); final SurfaceControl parent = animatable.getParentSurfaceControl(); + final SurfaceControl curAnimationLeash = animatable.getAnimationLeash(); // If the surface was destroyed or the leash is invalid, we don't care to reparent it back. // Note that we also set this variable to true even if the parent isn't valid anymore, in // order to ensure onAnimationLeashLost still gets called in this case. - final boolean reparent = surface != null; + // If the animation leash is set, and it is different from the removing leash, it means the + // surface now has a new animation surface. We don't want to reparent for that. + final boolean reparent = surface != null && (curAnimationLeash == null + || curAnimationLeash.equals(leash)); if (reparent) { if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent: " + parent); // We shouldn't really need these isValid checks but we do @@ -608,6 +637,14 @@ class SurfaceAnimator { void onAnimationLeashLost(Transaction t); /** + * Gets the last created animation leash that has not lost yet. + */ + @Nullable + default SurfaceControl getAnimationLeash() { + return null; + } + + /** * @return A new surface to be used for the animation leash, inserted at the correct * position in the hierarchy. */ diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java index e0a791e118bb..a7ef36b01d91 100644 --- a/services/core/java/com/android/server/wm/SurfaceFreezer.java +++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java @@ -17,22 +17,21 @@ package com.android.server.wm; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; -import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.GraphicBuffer; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; -import android.view.Surface; +import android.util.Slog; import android.view.SurfaceControl; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; -import java.util.function.Supplier; - /** * This class handles "freezing" of an Animatable. The Animatable in question should implement * Freezable. @@ -51,16 +50,19 @@ import java.util.function.Supplier; */ class SurfaceFreezer { - private final Freezable mAnimatable; - private final WindowManagerService mWmService; - private SurfaceControl mLeash; + private static final String TAG = "SurfaceFreezer"; + + private final @NonNull Freezable mAnimatable; + private final @NonNull WindowManagerService mWmService; + @VisibleForTesting + SurfaceControl mLeash; Snapshot mSnapshot = null; final Rect mFreezeBounds = new Rect(); /** * @param animatable The object to animate. */ - SurfaceFreezer(Freezable animatable, WindowManagerService service) { + SurfaceFreezer(@NonNull Freezable animatable, @NonNull WindowManagerService service) { mAnimatable = animatable; mWmService = service; } @@ -70,26 +72,34 @@ class SurfaceFreezer { * above the target surface) and then taking a snapshot and placing it over the target surface. * * @param startBounds The original bounds (on screen) of the surface we are snapshotting. + * @param relativePosition The related position of the snapshot surface to its parent. + * @param freezeTarget The surface to take snapshot from. If {@code null}, we will take a + * snapshot from the {@link #mAnimatable} surface. */ - void freeze(SurfaceControl.Transaction t, Rect startBounds) { + void freeze(SurfaceControl.Transaction t, Rect startBounds, Point relativePosition, + @Nullable SurfaceControl freezeTarget) { + reset(t); mFreezeBounds.set(startBounds); mLeash = SurfaceAnimator.createAnimationLeash(mAnimatable, mAnimatable.getSurfaceControl(), t, ANIMATION_TYPE_SCREEN_ROTATION, startBounds.width(), startBounds.height(), - startBounds.left, startBounds.top, false /* hidden */, + relativePosition.x, relativePosition.y, false /* hidden */, mWmService.mTransactionFactory); mAnimatable.onAnimationLeashCreated(t, mLeash); - SurfaceControl freezeTarget = mAnimatable.getFreezeSnapshotTarget(); + freezeTarget = freezeTarget != null ? freezeTarget : mAnimatable.getFreezeSnapshotTarget(); if (freezeTarget != null) { - SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = createSnapshotBuffer( + SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = createSnapshotBufferInner( freezeTarget, startBounds); final HardwareBuffer buffer = screenshotBuffer == null ? null : screenshotBuffer.getHardwareBuffer(); if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { + // This can happen when display is not ready. + Slog.w(TAG, "Failed to capture screenshot for " + mAnimatable); + unfreeze(t); return; } - mSnapshot = new Snapshot(mWmService.mSurfaceFactory, t, screenshotBuffer, mLeash); + mSnapshot = new Snapshot(t, screenshotBuffer, mLeash); } } @@ -104,12 +114,30 @@ class SurfaceFreezer { } /** + * Used by {@link SurfaceAnimator}. This "transfers" the snapshot leash to be used for + * animation. By transferring the leash, this will no longer try to clean-up the leash when + * finished. + */ + @Nullable + Snapshot takeSnapshotForAnimation() { + final Snapshot out = mSnapshot; + mSnapshot = null; + return out; + } + + /** * Clean-up the snapshot and remove leash. If the leash was taken, this just cleans-up the * snapshot. */ void unfreeze(SurfaceControl.Transaction t) { + unfreezeInner(t); + mAnimatable.onUnfrozen(); + } + + private void unfreezeInner(SurfaceControl.Transaction t) { if (mSnapshot != null) { mSnapshot.cancelAnimation(t, false /* restarting */); + mSnapshot = null; } if (mLeash == null) { return; @@ -117,12 +145,40 @@ class SurfaceFreezer { SurfaceControl leash = mLeash; mLeash = null; final boolean scheduleAnim = SurfaceAnimator.removeLeash(t, mAnimatable, leash, - false /* destroy */); + true /* destroy */); if (scheduleAnim) { mWmService.scheduleAnimationLocked(); } } + /** Resets the snapshot before taking another one if the animation hasn't been started yet. */ + private void reset(SurfaceControl.Transaction t) { + // Those would have been taken by the SurfaceAnimator if the animation has been started, so + // we can remove the leash directly. + // No need to reset the mAnimatable leash, as this is called before a new animation leash is + // created, so another #onAnimationLeashCreated will be called. + if (mSnapshot != null) { + mSnapshot.destroy(t); + mSnapshot = null; + } + if (mLeash != null) { + t.remove(mLeash); + mLeash = null; + } + } + + void setLayer(SurfaceControl.Transaction t, int layer) { + if (mLeash != null) { + t.setLayer(mLeash, layer); + } + } + + void setRelativeLayer(SurfaceControl.Transaction t, SurfaceControl relativeTo, int layer) { + if (mLeash != null) { + t.setRelativeLayer(mLeash, relativeTo, layer); + } + } + boolean hasLeash() { return mLeash != null; } @@ -143,21 +199,29 @@ class SurfaceFreezer { return SurfaceControl.captureLayers(captureArgs); } + @VisibleForTesting + SurfaceControl.ScreenshotHardwareBuffer createSnapshotBufferInner( + SurfaceControl target, Rect bounds) { + return createSnapshotBuffer(target, bounds); + } + + @VisibleForTesting + GraphicBuffer createFromHardwareBufferInner( + SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer) { + return GraphicBuffer.createFromHardwareBuffer(screenshotBuffer.getHardwareBuffer()); + } + class Snapshot { private SurfaceControl mSurfaceControl; private AnimationAdapter mAnimation; - private SurfaceAnimator.OnAnimationFinishedCallback mFinishedCallback; /** * @param t Transaction to create the thumbnail in. * @param screenshotBuffer A thumbnail or placeholder for thumbnail to initialize with. */ - Snapshot(Supplier<Surface> surfaceFactory, SurfaceControl.Transaction t, + Snapshot(SurfaceControl.Transaction t, SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer, SurfaceControl parent) { - // We can't use a delegating constructor since we need to - // reference this::onAnimationFinished - GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer( - screenshotBuffer.getHardwareBuffer()); + GraphicBuffer graphicBuffer = createFromHardwareBufferInner(screenshotBuffer); mSurfaceControl = mAnimatable.makeAnimationLeash() .setName("snapshot anim: " + mAnimatable.toString()) @@ -194,19 +258,15 @@ class SurfaceFreezer { * component responsible for running the animation. It runs the animation with * {@link AnimationAdapter#startAnimation} once the hierarchy with * the Leash has been set up. - * @param animationFinishedCallback The callback being triggered when the animation - * finishes. */ - void startAnimation(SurfaceControl.Transaction t, AnimationAdapter anim, int type, - @Nullable SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback) { + void startAnimation(SurfaceControl.Transaction t, AnimationAdapter anim, int type) { cancelAnimation(t, true /* restarting */); mAnimation = anim; - mFinishedCallback = animationFinishedCallback; if (mSurfaceControl == null) { cancelAnimation(t, false /* restarting */); return; } - mAnimation.startAnimation(mSurfaceControl, t, type, animationFinishedCallback); + mAnimation.startAnimation(mSurfaceControl, t, type, (typ, ani) -> { }); } /** @@ -218,18 +278,9 @@ class SurfaceFreezer { void cancelAnimation(SurfaceControl.Transaction t, boolean restarting) { final SurfaceControl leash = mSurfaceControl; final AnimationAdapter animation = mAnimation; - final SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback = - mFinishedCallback; mAnimation = null; - mFinishedCallback = null; if (animation != null) { animation.onAnimationCancelled(leash); - if (!restarting) { - if (animationFinishedCallback != null) { - animationFinishedCallback.onAnimationFinished( - ANIMATION_TYPE_APP_TRANSITION, animation); - } - } } if (!restarting) { destroy(t); @@ -244,5 +295,8 @@ class SurfaceFreezer { * will be generated (but the rest of the freezing logic will still happen). */ @Nullable SurfaceControl getFreezeSnapshotTarget(); + + /** Called when the {@link #unfreeze(SurfaceControl.Transaction)} is called. */ + void onUnfrozen(); } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index cc36e9e2282b..ba5e32891255 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -27,13 +27,10 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP; -import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; @@ -43,7 +40,6 @@ import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; -import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY; @@ -53,9 +49,6 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.view.Display.DEFAULT_DISPLAY; @@ -67,7 +60,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; -import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; @@ -84,22 +76,18 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS; -import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN; +import static com.android.server.wm.ActivityRecord.State.INITIALIZING; +import static com.android.server.wm.ActivityRecord.State.PAUSED; +import static com.android.server.wm.ActivityRecord.State.PAUSING; +import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.ActivityRecord.State.STARTED; import static com.android.server.wm.ActivityRecord.TRANSFER_SPLASH_SCREEN_COPYING; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_APP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CLEANUP; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_LOCKTASK; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_PAUSE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECENTS; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STATES; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION; @@ -112,7 +100,6 @@ import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME; import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS; -import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList; import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity; import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; @@ -123,21 +110,12 @@ import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_PINNABLE; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; -import static com.android.server.wm.Task.ActivityState.PAUSED; -import static com.android.server.wm.Task.ActivityState.PAUSING; -import static com.android.server.wm.Task.ActivityState.RESUMED; -import static com.android.server.wm.Task.ActivityState.STARTED; -import static com.android.server.wm.Task.ActivityState.STOPPING; -import static com.android.server.wm.TaskProto.ACTIVITY_TYPE; import static com.android.server.wm.TaskProto.AFFINITY; import static com.android.server.wm.TaskProto.BOUNDS; import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER; -import static com.android.server.wm.TaskProto.DISPLAY_ID; import static com.android.server.wm.TaskProto.FILLS_PARENT; import static com.android.server.wm.TaskProto.HAS_CHILD_PIP_ACTIVITY; import static com.android.server.wm.TaskProto.LAST_NON_FULLSCREEN_BOUNDS; -import static com.android.server.wm.TaskProto.MIN_HEIGHT; -import static com.android.server.wm.TaskProto.MIN_WIDTH; import static com.android.server.wm.TaskProto.ORIG_ACTIVITY; import static com.android.server.wm.TaskProto.REAL_ACTIVITY; import static com.android.server.wm.TaskProto.RESIZE_MODE; @@ -145,15 +123,13 @@ import static com.android.server.wm.TaskProto.RESUMED_ACTIVITY; import static com.android.server.wm.TaskProto.ROOT_TASK_ID; import static com.android.server.wm.TaskProto.SURFACE_HEIGHT; import static com.android.server.wm.TaskProto.SURFACE_WIDTH; -import static com.android.server.wm.TaskProto.WINDOW_CONTAINER; +import static com.android.server.wm.TaskProto.TASK_FRAGMENT; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; -import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainerChildProto.TASK; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.dipToPixel; -import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM; import static java.lang.Integer.MAX_VALUE; @@ -170,14 +146,8 @@ import android.app.AppGlobals; import android.app.IActivityController; import android.app.PictureInPictureParams; import android.app.RemoteAction; -import android.app.ResultInfo; import android.app.TaskInfo; import android.app.WindowConfiguration; -import android.app.servertransaction.ActivityResultItem; -import android.app.servertransaction.ClientTransaction; -import android.app.servertransaction.NewIntentItem; -import android.app.servertransaction.PauseActivityItem; -import android.app.servertransaction.ResumeActivityItem; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -185,6 +155,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; @@ -194,6 +165,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; @@ -208,10 +180,11 @@ import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; +import android.view.InsetsState; import android.view.RemoteAnimationAdapter; -import android.view.RemoteAnimationTarget; import android.view.Surface; import android.view.SurfaceControl; +import android.view.TaskTransitionSpec; import android.view.WindowManager; import android.view.WindowManager.TransitionOldType; import android.window.ITaskOrganizer; @@ -246,23 +219,21 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; -class Task extends WindowContainer<WindowContainer> { +/** + * {@link Task} is a TaskFragment that can contain a group of activities to perform a certain job. + * Activities of the same task affinities usually group in the same {@link Task}. A {@link Task} + * can also be an entity that showing in the Recents Screen for a job that user interacted with. + * A {@link Task} can also contain other {@link Task}s. + */ +class Task extends TaskFragment { private static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_ATM; - static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE; private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; - private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK; static final String TAG_TASKS = TAG + POSTFIX_TASKS; - private static final String TAG_APP = TAG + POSTFIX_APP; static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP; - private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE; - private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS; - private static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK; - private static final String TAG_STATES = TAG + POSTFIX_STATES; private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; private static final String TAG_TRANSITION = TAG + POSTFIX_TRANSITION; private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING; @@ -305,10 +276,6 @@ class Task extends WindowContainer<WindowContainer> { private static final String ATTR_LAST_SNAPSHOT_CONTENT_INSETS = "last_snapshot_content_insets"; private static final String ATTR_LAST_SNAPSHOT_BUFFER_SIZE = "last_snapshot_buffer_size"; - // Set to false to disable the preview that is shown while a new activity - // is being started. - private static final boolean SHOW_APP_STARTING_PREVIEW = true; - // How long to wait for all background Activities to redraw following a call to // convertToTranslucent(). private static final long TRANSLUCENT_CONVERSION_TIMEOUT = 2000; @@ -320,7 +287,6 @@ class Task extends WindowContainer<WindowContainer> { //ActivityTrigger static final ActivityTrigger mActivityTrigger = new ActivityTrigger(); - static final int INVALID_MIN_SIZE = -1; private float mShadowRadius = 0; /** @@ -340,36 +306,6 @@ class Task extends WindowContainer<WindowContainer> { // Do not move the root task as a part of reparenting static final int REPARENT_LEAVE_ROOT_TASK_IN_PLACE = 2; - @IntDef(prefix = {"TASK_VISIBILITY"}, value = { - TASK_VISIBILITY_VISIBLE, - TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, - TASK_VISIBILITY_INVISIBLE, - }) - @interface TaskVisibility {} - - /** Task is visible. No other tasks on top that fully or partially occlude it. */ - static final int TASK_VISIBILITY_VISIBLE = 0; - - /** Task is partially occluded by other translucent task(s) on top of it. */ - static final int TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT = 1; - - /** Task is completely invisible. */ - static final int TASK_VISIBILITY_INVISIBLE = 2; - - enum ActivityState { - INITIALIZING, - STARTED, - RESUMED, - PAUSING, - PAUSED, - STOPPING, - STOPPED, - FINISHING, - DESTROYING, - DESTROYED, - RESTARTING_PROCESS - } - public BoostFramework mPerf = null; // The topmost Activity passed to convertToTranslucent(). When non-null it means we are @@ -392,6 +328,11 @@ class Task extends WindowContainer<WindowContainer> { */ boolean mInResumeTopActivity = false; + /** + * Used to identify if the activity that is installed from device's system image. + */ + boolean mIsEffectivelySystemApp; + int mCurrentUser; String affinity; // The affinity name for this task, or null; may change identity. @@ -453,7 +394,6 @@ class Task extends WindowContainer<WindowContainer> { CharSequence lastDescription; // Last description captured for this item. - Task mAdjacentTask; // Task adjacent to this one. int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent. Task mPrevAffiliate; // previous task in affiliated chain. int mPrevAffiliateTaskId = INVALID_TASK_ID; // previous id for persistence. @@ -465,21 +405,12 @@ class Task extends WindowContainer<WindowContainer> { String mCallingPackage; String mCallingFeatureId; - private final Rect mTmpStableBounds = new Rect(); - private final Rect mTmpNonDecorBounds = new Rect(); - private final Rect mTmpBounds = new Rect(); - private final Rect mTmpInsets = new Rect(); - private final Rect mTmpFullBounds = new Rect(); private static final Rect sTmpBounds = new Rect(); // Last non-fullscreen bounds the task was launched in or resized to. // The information is persisted and used to determine the appropriate root task to launch the // task into on restore. Rect mLastNonFullscreenBounds = null; - // Minimal width and height of this task when it's resizeable. -1 means it should use the - // default minimal width/height. - int mMinWidth; - int mMinHeight; // The surface transition of the target when recents animation is finished. // This is originally introduced to carry out the current surface control position and window @@ -504,10 +435,6 @@ class Task extends WindowContainer<WindowContainer> { /** Used by fillTaskInfo */ final TaskActivitiesReport mReuseActivitiesReport = new TaskActivitiesReport(); - final ActivityTaskManagerService mAtmService; - final ActivityTaskSupervisor mTaskSupervisor; - final RootWindowContainer mRootWindowContainer; - /* Unique identifier for this task. */ final int mTaskId; /* User for which this task was created. */ @@ -566,9 +493,7 @@ class Task extends WindowContainer<WindowContainer> { // root task moves and we in fact do so when moving from full screen to pinned. private boolean mPreserveNonFloatingState = false; - private Dimmer mDimmer = new Dimmer(this); private final Rect mTmpDimBoundsRect = new Rect(); - private final Point mLastSurfaceSize = new Point(); /** @see #setCanAffectSystemUiFlags */ private boolean mCanAffectSystemUiFlags = true; @@ -578,29 +503,6 @@ class Task extends WindowContainer<WindowContainer> { /** ActivityRecords that are exiting, but still on screen for animations. */ final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>(); - /** - * When we are in the process of pausing an activity, before starting the - * next one, this variable holds the activity that is currently being paused. - * - * Only set at leaf tasks. - */ - @Nullable - private ActivityRecord mPausingActivity = null; - - /** - * This is the last activity that we put into the paused state. This is - * used to determine if we need to do an activity transition while sleeping, - * when we normally hold the top activity paused. - */ - ActivityRecord mLastPausedActivity = null; - - /** - * Current activity that is resumed, or null if there is none. - * Only set at leaf tasks. - */ - @Nullable - private ActivityRecord mResumedActivity = null; - private boolean mForceShowForAllUsers; /** When set, will force the task to report as invisible. */ @@ -651,121 +553,6 @@ class Task extends WindowContainer<WindowContainer> { } private static final ResetTargetTaskHelper sResetTargetTaskHelper = new ResetTargetTaskHelper(); - private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper = - new EnsureActivitiesVisibleHelper(this); - private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper = - new EnsureVisibleActivitiesConfigHelper(); - private class EnsureVisibleActivitiesConfigHelper { - private boolean mUpdateConfig; - private boolean mPreserveWindow; - private boolean mBehindFullscreen; - - void reset(boolean preserveWindow) { - mPreserveWindow = preserveWindow; - mUpdateConfig = false; - mBehindFullscreen = false; - } - - void process(ActivityRecord start, boolean preserveWindow) { - if (start == null || !start.mVisibleRequested) { - return; - } - reset(preserveWindow); - - final PooledFunction f = PooledLambda.obtainFunction( - EnsureVisibleActivitiesConfigHelper::processActivity, this, - PooledLambda.__(ActivityRecord.class)); - forAllActivities(f, start, true /*includeBoundary*/, true /*traverseTopToBottom*/); - f.recycle(); - - if (mUpdateConfig) { - // Ensure the resumed state of the focus activity if we updated the configuration of - // any activity. - mRootWindowContainer.resumeFocusedTasksTopActivities(); - } - } - - boolean processActivity(ActivityRecord r) { - mUpdateConfig |= r.ensureActivityConfiguration(0 /*globalChanges*/, mPreserveWindow); - mBehindFullscreen |= r.occludesParent(); - return mBehindFullscreen; - } - } - - private final CheckBehindFullscreenActivityHelper mCheckBehindFullscreenActivityHelper = - new CheckBehindFullscreenActivityHelper(); - private class CheckBehindFullscreenActivityHelper { - private boolean mAboveTop; - private boolean mBehindFullscreenActivity; - private ActivityRecord mToCheck; - private Consumer<ActivityRecord> mHandleBehindFullscreenActivity; - private boolean mHandlingOccluded; - - private void reset(ActivityRecord toCheck, - Consumer<ActivityRecord> handleBehindFullscreenActivity) { - mToCheck = toCheck; - mHandleBehindFullscreenActivity = handleBehindFullscreenActivity; - mAboveTop = true; - mBehindFullscreenActivity = false; - - if (!shouldBeVisible(null)) { - // The root task is not visible, so no activity in it should be displaying a - // starting window. Mark all activities below top and behind fullscreen. - mAboveTop = false; - mBehindFullscreenActivity = true; - } - - mHandlingOccluded = mToCheck == null && mHandleBehindFullscreenActivity != null; - } - - boolean process(ActivityRecord toCheck, - Consumer<ActivityRecord> handleBehindFullscreenActivity) { - reset(toCheck, handleBehindFullscreenActivity); - - if (!mHandlingOccluded && mBehindFullscreenActivity) { - return true; - } - - final ActivityRecord topActivity = topRunningActivity(); - final PooledFunction f = PooledLambda.obtainFunction( - CheckBehindFullscreenActivityHelper::processActivity, this, - PooledLambda.__(ActivityRecord.class), topActivity); - forAllActivities(f); - f.recycle(); - - return mBehindFullscreenActivity; - } - - /** Returns {@code true} to stop the outer loop and indicate the result is computed. */ - private boolean processActivity(ActivityRecord r, ActivityRecord topActivity) { - if (mAboveTop) { - if (r == topActivity) { - if (r == mToCheck) { - // It is the top activity in a visible root task. - mBehindFullscreenActivity = false; - return true; - } - mAboveTop = false; - } - mBehindFullscreenActivity |= r.occludesParent(); - return false; - } - - if (mHandlingOccluded) { - // Iterating through all occluded activities. - if (mBehindFullscreenActivity) { - mHandleBehindFullscreenActivity.accept(r); - } - } else if (r == mToCheck) { - return true; - } else if (mBehindFullscreenActivity) { - // It is occluded before {@param toCheck} is found. - return true; - } - mBehindFullscreenActivity |= r.occludesParent(); - return false; - } - } private final FindRootHelper mFindRootHelper = new FindRootHelper(); private class FindRootHelper { @@ -795,11 +582,24 @@ class Task extends WindowContainer<WindowContainer> { if (r.finishing) return false; - // Set this as the candidate root since it isn't finishing. - mRoot = r; + if (mRoot == null || mRoot.finishing) { + // Set this as the candidate root since it isn't finishing. + mRoot = r; + } + + final int uid = mRoot == r ? effectiveUid : r.info.applicationInfo.uid; + if (ignoreRelinquishIdentity + || (mRoot.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0 + || (mRoot.info.applicationInfo.uid != Process.SYSTEM_UID + && !mRoot.info.applicationInfo.isSystemApp() + && mRoot.info.applicationInfo.uid != uid)) { + // No need to relinquish identity, end search. + return true; + } - // Only end search if we are ignore relinquishing identity or we are not relinquishing. - return ignoreRelinquishIdentity || (r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0; + // Relinquish to next activity + mRoot = r; + return false; } } @@ -819,28 +619,6 @@ class Task extends WindowContainer<WindowContainer> { // false. private boolean mDeferTaskAppear; - /** - * Forces this task to be unorganized. Currently it is used for deferring the control of - * organizer when windowing mode is changing from PiP to fullscreen with orientation change. - * It is true only during Task#setWindowingMode ~ DisplayRotation#continueRotation. - * - * TODO(b/179235349): Remove this field by making surface operations from task organizer sync - * with display rotation. - */ - private boolean mForceNotOrganized; - - /** - * This task was created by the task organizer which has the following implementations. - * <ul> - * <lis>The task won't be removed when it is empty. Removal has to be an explicit request - * from the task organizer.</li> - * <li>Unlike other non-root tasks, it's direct children are visible to the task - * organizer for ordering purposes.</li> - * </ul> - */ - @VisibleForTesting - boolean mCreatedByOrganizer; - // Tracking cookie for the creation of this task. IBinder mLaunchCookie; @@ -868,11 +646,8 @@ class Task extends WindowContainer<WindowContainer> { IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor, boolean _createdByOrganizer, IBinder _launchCookie, boolean _deferTaskAppear, boolean _removeWithTaskOrganizer) { - super(atmService.mWindowManager); + super(atmService, null /* fragmentToken */, _createdByOrganizer, false /* isEmbedded */); - mAtmService = atmService; - mTaskSupervisor = atmService.mTaskSupervisor; - mRootWindowContainer = mAtmService.mRootWindowContainer; mTaskId = _taskId; mUserId = _userId; mResizeMode = resizeMode; @@ -885,7 +660,6 @@ class Task extends WindowContainer<WindowContainer> { : new PersistedTaskSnapshotData(); // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED). setOrientation(SCREEN_ORIENTATION_UNSET); - mRemoteToken = new RemoteToken(this); affinityIntent = _affinityIntent; affinity = _affinity; rootAffinity = _rootAffinity; @@ -923,7 +697,6 @@ class Task extends WindowContainer<WindowContainer> { mHandler = new ActivityTaskHandler(mTaskSupervisor.mLooper); mCurrentUser = mAtmService.mAmInternal.getCurrentUserId(); - mCreatedByOrganizer = _createdByOrganizer; mLaunchCookie = _launchCookie; mDeferTaskAppear = _deferTaskAppear; mRemoveWithTaskOrganizer = _removeWithTaskOrganizer; @@ -952,13 +725,13 @@ class Task extends WindowContainer<WindowContainer> { return this; } - private void cleanUpResourcesForDestroy(ConfigurationContainer oldParent) { + private void cleanUpResourcesForDestroy(WindowContainer<?> oldParent) { if (hasChild()) { return; } // This task is going away, so save the last state if necessary. - saveLaunchingStateIfNeeded(((WindowContainer) oldParent).getDisplayContent()); + saveLaunchingStateIfNeeded(oldParent.getDisplayContent()); // TODO: VI what about activity? final boolean isVoiceSession = voiceSession != null; @@ -968,7 +741,7 @@ class Task extends WindowContainer<WindowContainer> { } catch (RemoteException e) { } } - if (autoRemoveFromRecents() || isVoiceSession) { + if (autoRemoveFromRecents(oldParent.asTaskFragment()) || isVoiceSession) { // Task creator asked to remove this when done, or this task was a voice // interaction, so it should not remain on the recent tasks list. mTaskSupervisor.mRecentTasks.remove(this); @@ -1251,12 +1024,19 @@ class Task extends WindowContainer<WindowContainer> { * @param info The activity info which could be different from {@code r.info} if set. */ void setIntent(ActivityRecord r, @Nullable Intent intent, @Nullable ActivityInfo info) { - if (this.intent == null || !mNeverRelinquishIdentity) { + boolean updateIdentity = false; + if (this.intent == null) { + updateIdentity = true; + } else if (!mNeverRelinquishIdentity) { + final ActivityInfo activityInfo = info != null ? info : r.info; + updateIdentity = (effectiveUid == Process.SYSTEM_UID || mIsEffectivelySystemApp + || effectiveUid == activityInfo.applicationInfo.uid); + } + if (updateIdentity) { mCallingUid = r.launchedFromUid; mCallingPackage = r.launchedFromPackage; mCallingFeatureId = r.launchedFromFeatureId; setIntent(intent != null ? intent : r.intent, info != null ? info : r.info); - return; } setLockTaskAuth(r); } @@ -1274,6 +1054,7 @@ class Task extends WindowContainer<WindowContainer> { rootAffinity = affinity; } effectiveUid = info.applicationInfo.uid; + mIsEffectivelySystemApp = info.applicationInfo.isSystemApp(); stringName = null; if (info.targetActivity == null) { @@ -1372,7 +1153,11 @@ class Task extends WindowContainer<WindowContainer> { if (inMultiWindowMode() || !hasChild()) return false; if (intent != null) { final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME; - return intent != null && (intent.getFlags() & returnHomeFlags) == returnHomeFlags; + final Task task = getDisplayArea() != null ? getDisplayArea().getRootHomeTask() : null; + final boolean isLockTaskModeViolation = task != null + && mAtmService.getLockTaskController().isLockTaskModeViolation(task); + return (intent.getFlags() & returnHomeFlags) == returnHomeFlags + && !isLockTaskModeViolation; } final Task bottomTask = getBottomMostTask(); return bottomTask != this && bottomTask.returnsToHomeRootTask(); @@ -1389,11 +1174,11 @@ class Task extends WindowContainer<WindowContainer> { } @Override - void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) { - final DisplayContent display = newParent != null - ? ((WindowContainer) newParent).getDisplayContent() : null; - final DisplayContent oldDisplay = oldParent != null - ? ((WindowContainer) oldParent).getDisplayContent() : null; + void onParentChanged(ConfigurationContainer rawNewParent, ConfigurationContainer rawOldParent) { + final WindowContainer<?> newParent = (WindowContainer<?>) rawNewParent; + final WindowContainer<?> oldParent = (WindowContainer<?>) rawOldParent; + final DisplayContent display = newParent != null ? newParent.getDisplayContent() : null; + final DisplayContent oldDisplay = oldParent != null ? oldParent.getDisplayContent() : null; mPrevDisplayId = (oldDisplay != null) ? oldDisplay.mDisplayId : INVALID_DISPLAY; @@ -1434,7 +1219,7 @@ class Task extends WindowContainer<WindowContainer> { } if (oldParent != null) { - final Task oldParentTask = ((WindowContainer) oldParent).asTask(); + final Task oldParentTask = oldParent.asTask(); if (oldParentTask != null) { final PooledConsumer c = PooledLambda.obtainConsumer( Task::cleanUpActivityReferences, oldParentTask, @@ -1452,6 +1237,12 @@ class Task extends WindowContainer<WindowContainer> { } if (newParent != null) { + // Surface of Task that will not be organized should be shown by default. + // See Task#showSurfaceOnCreation + if (!mCreatedByOrganizer && !canBeOrganized()) { + getSyncTransaction().show(mSurfaceControl); + } + // TODO: Ensure that this is actually necessary here // Notify the voice session if required if (voiceSession != null) { @@ -1476,64 +1267,62 @@ class Task extends WindowContainer<WindowContainer> { mRootWindowContainer.updateUIDsPresentOnDisplay(); } - void cleanUpActivityReferences(ActivityRecord r) { - // mPausingActivity is set at leaf task - if (mPausingActivity != null && mPausingActivity == r) { - mPausingActivity = null; - } - - if (mResumedActivity != null && mResumedActivity == r) { - setResumedActivity(null, "cleanUpActivityReferences"); - } - - final WindowContainer parent = getParent(); - if (parent != null && parent.asTask() != null) { - parent.asTask().cleanUpActivityReferences(r); - return; + @Override + @Nullable + ActivityRecord getTopResumedActivity() { + if (!isLeafTask()) { + for (int i = mChildren.size() - 1; i >= 0; --i) { + ActivityRecord resumedActivity = mChildren.get(i).asTask().getTopResumedActivity(); + if (resumedActivity != null) { + return resumedActivity; + } + } } - r.removeTimeouts(); - mExitingActivities.remove(r); - } - /** @return the currently resumed activity. */ - ActivityRecord getResumedActivity() { - if (isLeafTask()) { - return mResumedActivity; + final ActivityRecord taskResumedActivity = getResumedActivity(); + ActivityRecord topResumedActivity = null; + for (int i = mChildren.size() - 1; i >= 0; --i) { + final WindowContainer child = mChildren.get(i); + if (child.asTaskFragment() != null) { + topResumedActivity = child.asTaskFragment().getTopResumedActivity(); + } else if (taskResumedActivity != null + && child.asActivityRecord() == taskResumedActivity) { + topResumedActivity = taskResumedActivity; + } + if (topResumedActivity != null) { + return topResumedActivity; + } } - - final Task task = getTask(t -> t.mResumedActivity != null, true /* traverseTopToBottom */); - return task != null ? task.mResumedActivity : null; - } - - @VisibleForTesting - void setPausingActivity(ActivityRecord pausing) { - mPausingActivity = pausing; + return null; } - /** - * @return the currently pausing activity of this task or the topmost pausing activity of the - * child tasks - */ - ActivityRecord getPausingActivity() { - if (isLeafTask()) { - return mPausingActivity; + @Override + @Nullable + ActivityRecord getTopPausingActivity() { + if (!isLeafTask()) { + for (int i = mChildren.size() - 1; i >= 0; --i) { + ActivityRecord pausingActivity = mChildren.get(i).asTask().getTopPausingActivity(); + if (pausingActivity != null) { + return pausingActivity; + } + } } - final Task task = getTask(t -> t.mPausingActivity != null, true /* traverseTopToBottom */); - return task != null ? task.mPausingActivity : null; - } - - void setResumedActivity(ActivityRecord r, String reason) { - warnForNonLeafTask("setResumedActivity"); - if (mResumedActivity == r) { - return; + final ActivityRecord taskPausingActivity = getPausingActivity(); + ActivityRecord topPausingActivity = null; + for (int i = mChildren.size() - 1; i >= 0; --i) { + final WindowContainer child = mChildren.get(i); + if (child.asTaskFragment() != null) { + topPausingActivity = child.asTaskFragment().getTopPausingActivity(); + } else if (taskPausingActivity != null + && child.asActivityRecord() == taskPausingActivity) { + topPausingActivity = taskPausingActivity; + } + if (topPausingActivity != null) { + return topPausingActivity; + } } - - if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) Slog.d(TAG_ROOT_TASK, - "setResumedActivity task:" + this + " + from: " - + mResumedActivity + " to:" + r + " reason:" + reason); - mResumedActivity = r; - mTaskSupervisor.updateTopResumedActivityIfNeeded(); + return null; } void updateTaskMovement(boolean toTop, int position) { @@ -1572,11 +1361,6 @@ class Task extends WindowContainer<WindowContainer> { mTaskId, mUserId); } - void setAdjacentTask(Task adjacent) { - mAdjacentTask = adjacent; - adjacent.mAdjacentTask = this; - } - void setTaskToAffiliateWith(Task taskToAffiliateWith) { closeRecentsChain(); mAffiliatedTaskId = taskToAffiliateWith.mAffiliatedTaskId; @@ -1622,14 +1406,6 @@ class Task extends WindowContainer<WindowContainer> { return mFindRootHelper.findRoot(ignoreRelinquishIdentity, setToBottomIfNone); } - ActivityRecord getTopNonFinishingActivity() { - return getTopNonFinishingActivity(true /* includeOverlays */); - } - - ActivityRecord getTopNonFinishingActivity(boolean includeOverlays) { - return getTopActivity(false /*includeFinishing*/, includeOverlays); - } - ActivityRecord topRunningActivityLocked() { if (getParent() == null) { return null; @@ -1656,14 +1432,6 @@ class Task extends WindowContainer<WindowContainer> { window.getBaseType() == TYPE_APPLICATION_STARTING) != null); } - ActivityRecord topActivityWithStartingWindow() { - if (getParent() == null) { - return null; - } - return getActivity((r) -> r.mStartingWindowState == STARTING_WINDOW_SHOWN - && r.okToShowLocked()); - } - /** * Return the number of running activities, and the number of non-finishing/initializing * activities in the provided {@param reportOut} respectively. @@ -1685,22 +1453,7 @@ class Task extends WindowContainer<WindowContainer> { } @Override - public int getActivityType() { - final int applicationType = super.getActivityType(); - if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) { - return applicationType; - } - return getTopChild().getActivityType(); - } - - @Override void addChild(WindowContainer child, int index) { - // If this task had any child before we added this one. - boolean hadChild = hasChild(); - // getActivityType() looks at the top child, so we need to read the type before adding - // a new child in case the new child is on top and UNDEFINED. - final int activityType = getActivityType(); - index = getAdjustedChildPosition(child, index); super.addChild(child, index); @@ -1716,13 +1469,20 @@ class Task extends WindowContainer<WindowContainer> { // now that this record is in a new task. mRootWindowContainer.updateUIDsPresentOnDisplay(); - final ActivityRecord r = child.asActivityRecord(); - if (r == null) return; + // Only pass minimum dimensions for pure TaskFragment. Task's minimum dimensions must be + // passed from Task constructor. + final TaskFragment childTaskFrag = child.asTaskFragment(); + if (childTaskFrag != null && childTaskFrag.asTask() == null) { + childTaskFrag.setMinDimensions(mMinWidth, mMinHeight); + } + } - r.inHistory = true; + /** Called when an {@link ActivityRecord} is added as a descendant */ + void onDescendantActivityAdded(boolean hadActivity, int activityType, ActivityRecord r) { + warnForNonLeafTask("onDescendantActivityAdded"); // Only set this based on the first activity - if (!hadChild) { + if (!hadActivity) { if (r.getActivityType() == ACTIVITY_TYPE_UNDEFINED) { // Normally non-standard activity type for the activity record will be set when the // object is created, however we delay setting the standard application type until @@ -1746,10 +1506,6 @@ class Task extends WindowContainer<WindowContainer> { updateEffectiveIntent(); } - void addChild(ActivityRecord r) { - addChild(r, Integer.MAX_VALUE /* add on top */); - } - @Override void removeChild(WindowContainer child) { removeChild(child, "removeChild"); @@ -1769,7 +1525,7 @@ class Task extends WindowContainer<WindowContainer> { if (DEBUG_TASK_MOVEMENT) { Slog.d(TAG_WM, "removeChild: child=" + r + " reason=" + reason); } - super.removeChild(r); + super.removeChild(r, false /* removeSelfIfPossible */); if (inPinnedWindowingMode()) { // We normally notify listeners of task stack changes on pause, however root pinned task @@ -1799,7 +1555,10 @@ class Task extends WindowContainer<WindowContainer> { // Remove entire task if it doesn't have any activity left and it isn't marked for reuse // or created by task organizer. if (!isRootTask()) { - getRootTask().removeChild(this, reason); + final WindowContainer<?> parent = getParent(); + if (parent != null) { + parent.asTaskFragment().removeChild(this); + } } EventLogTags.writeWmTaskRemoved(mTaskId, "removeChild:" + reason + " last r=" + r + " in t=" + this); @@ -1831,11 +1590,12 @@ class Task extends WindowContainer<WindowContainer> { return count > 0; } - private boolean autoRemoveFromRecents() { + private boolean autoRemoveFromRecents(TaskFragment oldParentFragment) { // We will automatically remove the task either if it has explicitly asked for // this, or it is empty and has never contained an activity that got shown to - // the user. - return autoRemoveRecents || (!hasChild() && !getHasBeenVisible()); + // the user, or it was being embedded in another Task. + return autoRemoveRecents || (!hasChild() && !getHasBeenVisible() + || (oldParentFragment != null && oldParentFragment.isEmbedded())); } private void clearPinnedTaskIfNeed() { @@ -1859,9 +1619,15 @@ class Task extends WindowContainer<WindowContainer> { } else { forAllActivities((r) -> { if (r.finishing) return; - // TODO: figure-out how to avoid object creation due to capture of reason variable. - r.finishIfPossible(Activity.RESULT_CANCELED, - null /* resultData */, null /* resultGrants */, reason, false /* oomAdj */); + // Prevent the transition from being executed too early if the top activity is + // resumed but the mVisibleRequested of any other activity is true, the transition + // should wait until next activity resumed. + if (r.isState(RESUMED) || (r.isVisible() + && !mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CLOSE))) { + r.finishIfPossible(reason, false /* oomAdj */); + } else { + r.destroyIfPossible(reason); + } }); } } @@ -1995,32 +1761,6 @@ class Task extends WindowContainer<WindowContainer> { && supportsMultiWindowInDisplayArea(tda); } - boolean supportsMultiWindow() { - return supportsMultiWindowInDisplayArea(getDisplayArea()); - } - - /** - * @return whether this task supports multi-window if it is in the given - * {@link TaskDisplayArea}. - */ - boolean supportsMultiWindowInDisplayArea(@Nullable TaskDisplayArea tda) { - if (!mAtmService.mSupportsMultiWindow) { - return false; - } - if (tda == null) { - Slog.w(TAG_TASKS, "Can't find TaskDisplayArea to determine support for multi" - + " window. Task id=" + mTaskId + " attached=" + isAttached()); - return false; - } - - if (!isResizeable() && !tda.supportsNonResizableMultiWindow()) { - // Not support non-resizable in multi window. - return false; - } - - return tda.supportsActivityMinWidthHeightMultiWindow(mMinWidth, mMinHeight); - } - /** * Check whether this task can be launched on the specified display. * @@ -2156,60 +1896,6 @@ class Task extends WindowContainer<WindowContainer> { } } - void adjustForMinimalTaskDimensions(@NonNull Rect bounds, @NonNull Rect previousBounds, - @NonNull Configuration parentConfig) { - int minWidth = mMinWidth; - int minHeight = mMinHeight; - // If the task has no requested minimal size, we'd like to enforce a minimal size - // so that the user can not render the task too small to manipulate. We don't need - // to do this for the root pinned task as the bounds are controlled by the system. - if (!inPinnedWindowingMode()) { - final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp; - final float density = (float) parentConfig.densityDpi / DisplayMetrics.DENSITY_DEFAULT; - final int defaultMinSize = (int) (defaultMinSizeDp * density); - - if (minWidth == INVALID_MIN_SIZE) { - minWidth = defaultMinSize; - } - if (minHeight == INVALID_MIN_SIZE) { - minHeight = defaultMinSize; - } - } - if (bounds.isEmpty()) { - // If inheriting parent bounds, check if parent bounds adhere to minimum size. If they - // do, we can just skip. - final Rect parentBounds = parentConfig.windowConfiguration.getBounds(); - if (parentBounds.width() >= minWidth && parentBounds.height() >= minHeight) { - return; - } - bounds.set(parentBounds); - } - final boolean adjustWidth = minWidth > bounds.width(); - final boolean adjustHeight = minHeight > bounds.height(); - if (!(adjustWidth || adjustHeight)) { - return; - } - - if (adjustWidth) { - if (!previousBounds.isEmpty() && bounds.right == previousBounds.right) { - bounds.left = bounds.right - minWidth; - } else { - // Either left bounds match, or neither match, or the previous bounds were - // fullscreen and we default to keeping left. - bounds.right = bounds.left + minWidth; - } - } - if (adjustHeight) { - if (!previousBounds.isEmpty() && bounds.bottom == previousBounds.bottom) { - bounds.top = bounds.bottom - minHeight; - } else { - // Either top bounds match, or neither match, or the previous bounds were - // fullscreen and we default to keeping top. - bounds.bottom = bounds.top + minHeight; - } - } - } - void setLastNonFullscreenBounds(Rect bounds) { if (mLastNonFullscreenBounds == null) { mLastNonFullscreenBounds = new Rect(bounds); @@ -2218,32 +1904,6 @@ class Task extends WindowContainer<WindowContainer> { } } - /** - * This should be called when an child activity changes state. This should only - * be called from - * {@link ActivityRecord#setState(ActivityState, String)} . - * @param record The {@link ActivityRecord} whose state has changed. - * @param state The new state. - * @param reason The reason for the change. - */ - void onActivityStateChanged(ActivityRecord record, ActivityState state, String reason) { - warnForNonLeafTask("onActivityStateChanged"); - if (record == mResumedActivity && state != RESUMED) { - setResumedActivity(null, reason + " - onActivityStateChanged"); - } - - if (state == RESUMED) { - if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) { - Slog.v(TAG_ROOT_TASK, "set resumed activity to:" + record + " reason:" + reason); - } - setResumedActivity(record, reason + " - onActivityStateChanged"); - if (record == mRootWindowContainer.getTopResumedActivity()) { - mAtmService.setResumedActivityUncheckLocked(record, reason); - } - mTaskSupervisor.mRecentTasks.add(record.getTask()); - } - } - private void onConfigurationChangedInner(Configuration newParentConfig) { // Check if the new configuration supports persistent bounds (eg. is Freeform) and if so // restore the last recorded non-fullscreen bounds. @@ -2291,19 +1951,17 @@ class Task extends WindowContainer<WindowContainer> { } } - if (pipChanging) { - // If the top activity is using fixed rotation, it should be changing from PiP to - // fullscreen with display orientation change. Do not notify fullscreen task organizer - // because the restoration of task surface and the transformation of activity surface - // need to be done synchronously. + if (pipChanging && wasInPictureInPicture) { + // If the top activity is changing from PiP to fullscreen with fixed rotation, + // clear the crop and rotation matrix of task because fixed rotation will handle + // the transformation on activity level. This also avoids flickering caused by the + // latency of fullscreen task organizer configuring the surface. final ActivityRecord r = topRunningActivity(); if (r != null && mDisplayContent.isFixedRotationLaunchingApp(r)) { - mForceNotOrganized = true; + getSyncTransaction().setWindowCrop(mSurfaceControl, null) + .setCornerRadius(mSurfaceControl, 0f) + .setMatrix(mSurfaceControl, Matrix.IDENTITY_MATRIX, new float[9]); } - } else { - // If the display orientation change is done, let the corresponding task organizer take - // back the control of this task. - mForceNotOrganized = false; } saveLaunchingStateIfNeeded(); @@ -2362,14 +2020,7 @@ class Task extends WindowContainer<WindowContainer> { taskDisplayArea.onRootTaskWindowingModeChanged(this); } - if (mDisplayContent == null) { - return; - } - - // Use override windowing mode to prevent extra bounds changes if inheriting the mode. - final int overrideWindowingMode = getRequestedOverrideWindowingMode(); - if (overrideWindowingMode != WINDOWING_MODE_PINNED - && !getRequestedOverrideBounds().isEmpty()) { + if (!isOrganized() && !getRequestedOverrideBounds().isEmpty() && mDisplayContent != null) { // If the parent (display) has rotated, rotate our bounds to best-fit where their // bounds were on the pre-rotated display. final int newRotation = getWindowConfiguration().getRotation(); @@ -2388,21 +2039,158 @@ class Task extends WindowContainer<WindowContainer> { } } + void resolveLeafTaskOnlyOverrideConfigs(Configuration newParentConfig, Rect previousBounds) { + if (!isLeafTask()) { + return; + } + + int windowingMode = + getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); + if (windowingMode == WINDOWING_MODE_UNDEFINED) { + windowingMode = newParentConfig.windowConfiguration.getWindowingMode(); + } + // Commit the resolved windowing mode so the canSpecifyOrientation won't get the old + // mode that may cause the bounds to be miscalculated, e.g. letterboxed. + getConfiguration().windowConfiguration.setWindowingMode(windowingMode); + Rect outOverrideBounds = getResolvedOverrideConfiguration().windowConfiguration.getBounds(); + + if (windowingMode == WINDOWING_MODE_FULLSCREEN) { + // Use empty bounds to indicate "fill parent". + outOverrideBounds.setEmpty(); + // The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if + // the parent or display is smaller than the size, the content may be cropped. + return; + } + + adjustForMinimalTaskDimensions(outOverrideBounds, previousBounds, newParentConfig); + if (windowingMode == WINDOWING_MODE_FREEFORM) { + computeFreeformBounds(outOverrideBounds, newParentConfig); + return; + } + } + + void adjustForMinimalTaskDimensions(@NonNull Rect bounds, @NonNull Rect previousBounds, + @NonNull Configuration parentConfig) { + int minWidth = mMinWidth; + int minHeight = mMinHeight; + // If the task has no requested minimal size, we'd like to enforce a minimal size + // so that the user can not render the task fragment too small to manipulate. We don't need + // to do this for the root pinned task as the bounds are controlled by the system. + if (!inPinnedWindowingMode()) { + final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp; + final float density = (float) parentConfig.densityDpi / DisplayMetrics.DENSITY_DEFAULT; + final int defaultMinSize = (int) (defaultMinSizeDp * density); + + if (minWidth == INVALID_MIN_SIZE) { + minWidth = defaultMinSize; + } + if (minHeight == INVALID_MIN_SIZE) { + minHeight = defaultMinSize; + } + } + if (bounds.isEmpty()) { + // If inheriting parent bounds, check if parent bounds adhere to minimum size. If they + // do, we can just skip. + final Rect parentBounds = parentConfig.windowConfiguration.getBounds(); + if (parentBounds.width() >= minWidth && parentBounds.height() >= minHeight) { + return; + } + bounds.set(parentBounds); + } + final boolean adjustWidth = minWidth > bounds.width(); + final boolean adjustHeight = minHeight > bounds.height(); + if (!(adjustWidth || adjustHeight)) { + return; + } + + if (adjustWidth) { + if (!previousBounds.isEmpty() && bounds.right == previousBounds.right) { + bounds.left = bounds.right - minWidth; + } else { + // Either left bounds match, or neither match, or the previous bounds were + // fullscreen and we default to keeping left. + bounds.right = bounds.left + minWidth; + } + } + if (adjustHeight) { + if (!previousBounds.isEmpty() && bounds.bottom == previousBounds.bottom) { + bounds.top = bounds.bottom - minHeight; + } else { + // Either top bounds match, or neither match, or the previous bounds were + // fullscreen and we default to keeping top. + bounds.bottom = bounds.top + minHeight; + } + } + } + + /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */ + private void computeFreeformBounds(@NonNull Rect outBounds, + @NonNull Configuration newParentConfig) { + // by policy, make sure the window remains within parent somewhere + final float density = + ((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT; + final Rect parentBounds = + new Rect(newParentConfig.windowConfiguration.getBounds()); + final DisplayContent display = getDisplayContent(); + if (display != null) { + // If a freeform window moves below system bar, there is no way to move it again + // by touch. Because its caption is covered by system bar. So we exclude them + // from root task bounds. and then caption will be shown inside stable area. + final Rect stableBounds = new Rect(); + display.getStableRect(stableBounds); + parentBounds.intersect(stableBounds); + } + + fitWithinBounds(outBounds, parentBounds, + (int) (density * WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP), + (int) (density * WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP)); + + // Prevent to overlap caption with stable insets. + final int offsetTop = parentBounds.top - outBounds.top; + if (offsetTop > 0) { + outBounds.offset(0, offsetTop); + } + } + /** - * Initializes a change transition. See {@link SurfaceFreezer} for more information. + * Adjusts bounds to stay within root task bounds. + * + * Since bounds might be outside of root task bounds, this method tries to move the bounds in + * a way that keep them unchanged, but be contained within the root task bounds. + * + * @param bounds Bounds to be adjusted. + * @param rootTaskBounds Bounds within which the other bounds should remain. + * @param overlapPxX The amount of px required to be visible in the X dimension. + * @param overlapPxY The amount of px required to be visible in the Y dimension. */ - private void initializeChangeTransition(Rect startBounds) { - mDisplayContent.prepareAppTransition(TRANSIT_CHANGE); - mDisplayContent.mChangingContainers.add(this); + private static void fitWithinBounds(Rect bounds, Rect rootTaskBounds, int overlapPxX, + int overlapPxY) { + if (rootTaskBounds == null || rootTaskBounds.isEmpty() || rootTaskBounds.contains(bounds)) { + return; + } - mSurfaceFreezer.freeze(getPendingTransaction(), startBounds); + // For each side of the parent (eg. left), check if the opposing side of the window (eg. + // right) is at least overlap pixels away. If less, offset the window by that difference. + int horizontalDiff = 0; + // If window is smaller than overlap, use it's smallest dimension instead + int overlapLR = Math.min(overlapPxX, bounds.width()); + if (bounds.right < (rootTaskBounds.left + overlapLR)) { + horizontalDiff = overlapLR - (bounds.right - rootTaskBounds.left); + } else if (bounds.left > (rootTaskBounds.right - overlapLR)) { + horizontalDiff = -(overlapLR - (rootTaskBounds.right - bounds.left)); + } + int verticalDiff = 0; + int overlapTB = Math.min(overlapPxY, bounds.width()); + if (bounds.bottom < (rootTaskBounds.top + overlapTB)) { + verticalDiff = overlapTB - (bounds.bottom - rootTaskBounds.top); + } else if (bounds.top > (rootTaskBounds.bottom - overlapTB)) { + verticalDiff = -(overlapTB - (rootTaskBounds.bottom - bounds.top)); + } + bounds.offset(horizontalDiff, verticalDiff); } private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) { - if (mWmService.mDisableTransitionAnimation - || !isVisible() - || getSurfaceControl() == null - || !isLeafTask()) { + if (!isLeafTask() || !canStartChangeTransition()) { return false; } // Only do an animation into and out-of freeform mode for now. Other mode @@ -2536,400 +2324,6 @@ class Task extends WindowContainer<WindowContainer> { mTaskSupervisor.mLaunchParamsPersister.saveTask(this, display); } - /** - * Adjust bounds to stay within root task bounds. - * - * Since bounds might be outside of root task bounds, this method tries to move the bounds in - * a way that keep them unchanged, but be contained within the root task bounds. - * - * @param bounds Bounds to be adjusted. - * @param rootTaskBounds Bounds within which the other bounds should remain. - * @param overlapPxX The amount of px required to be visible in the X dimension. - * @param overlapPxY The amount of px required to be visible in the Y dimension. - */ - private static void fitWithinBounds(Rect bounds, Rect rootTaskBounds, int overlapPxX, - int overlapPxY) { - if (rootTaskBounds == null || rootTaskBounds.isEmpty() || rootTaskBounds.contains(bounds)) { - return; - } - - // For each side of the parent (eg. left), check if the opposing side of the window (eg. - // right) is at least overlap pixels away. If less, offset the window by that difference. - int horizontalDiff = 0; - // If window is smaller than overlap, use it's smallest dimension instead - int overlapLR = Math.min(overlapPxX, bounds.width()); - if (bounds.right < (rootTaskBounds.left + overlapLR)) { - horizontalDiff = overlapLR - (bounds.right - rootTaskBounds.left); - } else if (bounds.left > (rootTaskBounds.right - overlapLR)) { - horizontalDiff = -(overlapLR - (rootTaskBounds.right - bounds.left)); - } - int verticalDiff = 0; - int overlapTB = Math.min(overlapPxY, bounds.width()); - if (bounds.bottom < (rootTaskBounds.top + overlapTB)) { - verticalDiff = overlapTB - (bounds.bottom - rootTaskBounds.top); - } else if (bounds.top > (rootTaskBounds.bottom - overlapTB)) { - verticalDiff = -(overlapTB - (rootTaskBounds.bottom - bounds.top)); - } - bounds.offset(horizontalDiff, verticalDiff); - } - - /** - * Intersects inOutBounds with intersectBounds-intersectInsets. If inOutBounds is larger than - * intersectBounds on a side, then the respective side will not be intersected. - * - * The assumption is that if inOutBounds is initially larger than intersectBounds, then the - * inset on that side is no-longer applicable. This scenario happens when a task's minimal - * bounds are larger than the provided parent/display bounds. - * - * @param inOutBounds the bounds to intersect. - * @param intersectBounds the bounds to intersect with. - * @param intersectInsets insets to apply to intersectBounds before intersecting. - */ - static void intersectWithInsetsIfFits( - Rect inOutBounds, Rect intersectBounds, Rect intersectInsets) { - if (inOutBounds.right <= intersectBounds.right) { - inOutBounds.right = - Math.min(intersectBounds.right - intersectInsets.right, inOutBounds.right); - } - if (inOutBounds.bottom <= intersectBounds.bottom) { - inOutBounds.bottom = - Math.min(intersectBounds.bottom - intersectInsets.bottom, inOutBounds.bottom); - } - if (inOutBounds.left >= intersectBounds.left) { - inOutBounds.left = - Math.max(intersectBounds.left + intersectInsets.left, inOutBounds.left); - } - if (inOutBounds.top >= intersectBounds.top) { - inOutBounds.top = - Math.max(intersectBounds.top + intersectInsets.top, inOutBounds.top); - } - } - - /** - * Gets bounds with non-decor and stable insets applied respectively. - * - * If bounds overhangs the display, those edges will not get insets. See - * {@link #intersectWithInsetsIfFits} - * - * @param outNonDecorBounds where to place bounds with non-decor insets applied. - * @param outStableBounds where to place bounds with stable insets applied. - * @param bounds the bounds to inset. - */ - private void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds, - DisplayInfo displayInfo) { - outNonDecorBounds.set(bounds); - outStableBounds.set(bounds); - final Task rootTask = getRootTask(); - if (rootTask == null || rootTask.mDisplayContent == null) { - return; - } - mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight); - - final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy(); - policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth, - displayInfo.logicalHeight, displayInfo.displayCutout, mTmpInsets); - intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets); - - policy.convertNonDecorInsetsToStableInsets(mTmpInsets, displayInfo.rotation); - intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets); - } - - /** - * Forces the app bounds related configuration can be computed by - * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo, - * ActivityRecord.CompatDisplayInsets)}. - */ - private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) { - final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds(); - if (appBounds != null) { - appBounds.setEmpty(); - } - inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED; - inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; - } - - void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) { - if (overrideDisplayInfo != null) { - // Make sure the screen related configs can be computed by the provided display info. - inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED; - invalidateAppBoundsConfig(inOutConfig); - } - computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo, - null /* compatInsets */); - } - - void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig) { - computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */, - null /* compatInsets */); - } - - void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig, - @Nullable ActivityRecord.CompatDisplayInsets compatInsets) { - if (compatInsets != null) { - // Make sure the app bounds can be computed by the compat insets. - invalidateAppBoundsConfig(inOutConfig); - } - computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */, - compatInsets); - } - - /** - * Calculates configuration values used by the client to get resources. This should be run - * using app-facing bounds (bounds unmodified by animations or transient interactions). - * - * This assumes bounds are non-empty/null. For the null-bounds case, the caller is likely - * configuring an "inherit-bounds" window which means that all configuration settings would - * just be inherited from the parent configuration. - **/ - void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo, - @Nullable ActivityRecord.CompatDisplayInsets compatInsets) { - int windowingMode = inOutConfig.windowConfiguration.getWindowingMode(); - if (windowingMode == WINDOWING_MODE_UNDEFINED) { - windowingMode = parentConfig.windowConfiguration.getWindowingMode(); - } - - float density = inOutConfig.densityDpi; - if (density == Configuration.DENSITY_DPI_UNDEFINED) { - density = parentConfig.densityDpi; - } - density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; - - // The bounds may have been overridden at this level. If the parent cannot cover these - // bounds, the configuration is still computed according to the override bounds. - final boolean insideParentBounds; - - final Rect parentBounds = parentConfig.windowConfiguration.getBounds(); - final Rect resolvedBounds = inOutConfig.windowConfiguration.getBounds(); - if (resolvedBounds == null || resolvedBounds.isEmpty()) { - mTmpFullBounds.set(parentBounds); - insideParentBounds = true; - } else { - mTmpFullBounds.set(resolvedBounds); - insideParentBounds = parentBounds.contains(resolvedBounds); - } - - // Non-null compatibility insets means the activity prefers to keep its original size, so - // out bounds doesn't need to be restricted by the parent or current display - final boolean customContainerPolicy = compatInsets != null; - - Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - if (outAppBounds == null || outAppBounds.isEmpty()) { - // App-bounds hasn't been overridden, so calculate a value for it. - inOutConfig.windowConfiguration.setAppBounds(mTmpFullBounds); - outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - - if (!customContainerPolicy && windowingMode != WINDOWING_MODE_FREEFORM) { - final Rect containingAppBounds; - if (insideParentBounds) { - containingAppBounds = parentConfig.windowConfiguration.getAppBounds(); - } else { - // Restrict appBounds to display non-decor rather than parent because the - // override bounds are beyond the parent. Otherwise, it won't match the - // overridden bounds. - final TaskDisplayArea displayArea = getDisplayArea(); - containingAppBounds = displayArea != null - ? displayArea.getWindowConfiguration().getAppBounds() : null; - } - if (containingAppBounds != null && !containingAppBounds.isEmpty()) { - outAppBounds.intersect(containingAppBounds); - } - } - } - - if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED - || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) { - mTmpNonDecorBounds.set(mTmpFullBounds); - mTmpStableBounds.set(mTmpFullBounds); - } else if (!customContainerPolicy - && (overrideDisplayInfo != null || getDisplayContent() != null)) { - final DisplayInfo di = overrideDisplayInfo != null - ? overrideDisplayInfo - : getDisplayContent().getDisplayInfo(); - - // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen - // area, i.e. the screen area without the system bars. - // The non decor inset are areas that could never be removed in Honeycomb. See - // {@link WindowManagerPolicy#getNonDecorInsetsLw}. - calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di); - } else { - // Apply the given non-decor and stable insets to calculate the corresponding bounds - // for screen size of configuration. - int rotation = inOutConfig.windowConfiguration.getRotation(); - if (rotation == ROTATION_UNDEFINED) { - rotation = parentConfig.windowConfiguration.getRotation(); - } - if (rotation != ROTATION_UNDEFINED && customContainerPolicy) { - mTmpNonDecorBounds.set(mTmpFullBounds); - mTmpStableBounds.set(mTmpFullBounds); - compatInsets.getBoundsByRotation(mTmpBounds, rotation); - intersectWithInsetsIfFits(mTmpNonDecorBounds, mTmpBounds, - compatInsets.mNonDecorInsets[rotation]); - intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds, - compatInsets.mStableInsets[rotation]); - outAppBounds.set(mTmpNonDecorBounds); - } else { - // Set to app bounds because it excludes decor insets. - mTmpNonDecorBounds.set(outAppBounds); - mTmpStableBounds.set(outAppBounds); - } - } - - if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { - final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density); - inOutConfig.screenWidthDp = (insideParentBounds && !customContainerPolicy) - ? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp) - : overrideScreenWidthDp; - } - if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - final int overrideScreenHeightDp = (int) (mTmpStableBounds.height() / density); - inOutConfig.screenHeightDp = (insideParentBounds && !customContainerPolicy) - ? Math.min(overrideScreenHeightDp, parentConfig.screenHeightDp) - : overrideScreenHeightDp; - } - - if (inOutConfig.smallestScreenWidthDp - == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { - if (WindowConfiguration.isFloating(windowingMode)) { - // For floating tasks, calculate the smallest width from the bounds of the task - inOutConfig.smallestScreenWidthDp = (int) ( - Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density); - } - // otherwise, it will just inherit - } - } - - if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { - inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) - ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; - } - if (inOutConfig.screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) { - // For calculating screen layout, we need to use the non-decor inset screen area for the - // calculation for compatibility reasons, i.e. screen area without system bars that - // could never go away in Honeycomb. - int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density); - int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density); - // Use overrides if provided. If both overrides are provided, mTmpNonDecorBounds is - // undefined so it can't be used. - if (inOutConfig.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) { - compatScreenWidthDp = inOutConfig.screenWidthDp; - } - if (inOutConfig.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - compatScreenHeightDp = inOutConfig.screenHeightDp; - } - // Reducing the screen layout starting from its parent config. - inOutConfig.screenLayout = computeScreenLayoutOverride(parentConfig.screenLayout, - compatScreenWidthDp, compatScreenHeightDp); - } - } - - /** Computes LONG, SIZE and COMPAT parts of {@link Configuration#screenLayout}. */ - static int computeScreenLayoutOverride(int sourceScreenLayout, int screenWidthDp, - int screenHeightDp) { - sourceScreenLayout = sourceScreenLayout - & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK); - final int longSize = Math.max(screenWidthDp, screenHeightDp); - final int shortSize = Math.min(screenWidthDp, screenHeightDp); - return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize); - } - - @Override - void resolveOverrideConfiguration(Configuration newParentConfig) { - mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds()); - super.resolveOverrideConfiguration(newParentConfig); - - int windowingMode = - getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); - final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode(); - - // Resolve override windowing mode to fullscreen for home task (even on freeform - // display), or split-screen if in split-screen mode. - if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) { - windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode) - ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN; - getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode); - } - - // Do not allow tasks not support multi window to be in a multi-window mode, unless it is in - // pinned windowing mode. - if (!supportsMultiWindow()) { - final int candidateWindowingMode = - windowingMode != WINDOWING_MODE_UNDEFINED ? windowingMode : parentWindowingMode; - if (WindowConfiguration.inMultiWindowMode(candidateWindowingMode) - && candidateWindowingMode != WINDOWING_MODE_PINNED) { - getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode( - WINDOWING_MODE_FULLSCREEN); - } - } - - if (isLeafTask()) { - resolveLeafOnlyOverrideConfigs(newParentConfig, mTmpBounds /* previousBounds */); - } - computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig); - } - - private void resolveLeafOnlyOverrideConfigs(Configuration newParentConfig, - Rect previousBounds) { - - int windowingMode = - getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); - if (windowingMode == WINDOWING_MODE_UNDEFINED) { - windowingMode = newParentConfig.windowConfiguration.getWindowingMode(); - } - // Commit the resolved windowing mode so the canSpecifyOrientation won't get the old - // mode that may cause the bounds to be miscalculated, e.g. letterboxed. - getConfiguration().windowConfiguration.setWindowingMode(windowingMode); - Rect outOverrideBounds = - getResolvedOverrideConfiguration().windowConfiguration.getBounds(); - - if (windowingMode == WINDOWING_MODE_FULLSCREEN) { - // Use empty bounds to indicate "fill parent". - outOverrideBounds.setEmpty(); - // The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if - // the parent or display is smaller than the size, the content may be cropped. - return; - } - - adjustForMinimalTaskDimensions(outOverrideBounds, previousBounds, newParentConfig); - if (windowingMode == WINDOWING_MODE_FREEFORM) { - computeFreeformBounds(outOverrideBounds, newParentConfig); - return; - } - } - - /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */ - private void computeFreeformBounds(@NonNull Rect outBounds, - @NonNull Configuration newParentConfig) { - // by policy, make sure the window remains within parent somewhere - final float density = - ((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT; - final Rect parentBounds = - new Rect(newParentConfig.windowConfiguration.getBounds()); - final DisplayContent display = getDisplayContent(); - if (display != null) { - // If a freeform window moves below system bar, there is no way to move it again - // by touch. Because its caption is covered by system bar. So we exclude them - // from root task bounds. and then caption will be shown inside stable area. - final Rect stableBounds = new Rect(); - display.getStableRect(stableBounds); - parentBounds.intersect(stableBounds); - } - - fitWithinBounds(outBounds, parentBounds, - (int) (density * WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP), - (int) (density * WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP)); - - // Prevent to overlap caption with stable insets. - final int offsetTop = parentBounds.top - outBounds.top; - if (offsetTop > 0) { - outBounds.offset(0, offsetTop); - } - } - Rect updateOverrideConfigurationFromLaunchBounds() { // If the task is controlled by another organized task, do not set override // configurations and let its parent (organized task) to control it; @@ -2978,24 +2372,11 @@ class Task extends WindowContainer<WindowContainer> { } } - int getDisplayId() { - final DisplayContent dc = getDisplayContent(); - return dc != null ? dc.mDisplayId : INVALID_DISPLAY; - } - /** @return Id of root task. */ int getRootTaskId() { return getRootTask().mTaskId; } - Task getRootTask() { - final WindowContainer parent = getParent(); - if (parent == null) return this; - - final Task parentTask = parent.asTask(); - return parentTask == null ? this : parentTask.getRootTask(); - } - /** @return the first organized task. */ @Nullable Task getOrganizedTask() { @@ -3024,6 +2405,16 @@ class Task extends WindowContainer<WindowContainer> { return true; } + /** Return the top-most leaf-task under this one, or this task if it is a leaf. */ + public Task getTopLeafTask() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + final Task child = mChildren.get(i).asTask(); + if (child == null) continue; + return child.getTopLeafTask(); + } + return this; + } + int getDescendantTaskCount() { final int[] currentCount = {0}; final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; }, @@ -3108,12 +2499,12 @@ class Task extends WindowContainer<WindowContainer> { // and focused application if needed. focusableTask.moveToFront(myReason); // Top display focused root task is changed, update top resumed activity if needed. - if (rootTask.getResumedActivity() != null) { + if (rootTask.getTopResumedActivity() != null) { mTaskSupervisor.updateTopResumedActivityIfNeeded(); // Set focused app directly because if the next focused activity is already resumed // (e.g. the next top activity is on a different display), there won't have activity // state change to update it. - mAtmService.setResumedActivityUncheckLocked(rootTask.getResumedActivity(), reason); + mAtmService.setResumedActivityUncheckLocked(rootTask.getTopResumedActivity(), reason); } return rootTask; } @@ -3162,17 +2553,16 @@ class Task extends WindowContainer<WindowContainer> { // Figure-out min/max possible position depending on if child can show for current user. int minPosition = (canShowChild) ? computeMinUserPosition(0, size) : 0; - int maxPosition = (canShowChild) ? size - 1 : computeMaxUserPosition(size - 1); - if (!hasChild(wc)) { - // Increase the maxPosition because children size will grow once wc is added. - ++maxPosition; + int maxPosition = minPosition; + if (size > 0) { + maxPosition = (canShowChild) ? size - 1 : computeMaxUserPosition(size - 1); } // Factor in always-on-top children in max possible position. if (!wc.isAlwaysOnTop()) { // We want to place all non-always-on-top containers below always-on-top ones. while (maxPosition > minPosition) { - if (!mChildren.get(maxPosition - 1).isAlwaysOnTop()) break; + if (!mChildren.get(maxPosition).isAlwaysOnTop()) break; --maxPosition; } } @@ -3183,6 +2573,12 @@ class Task extends WindowContainer<WindowContainer> { } else if (suggestedPosition == POSITION_TOP && maxPosition >= (size - 1)) { return POSITION_TOP; } + + // Increase the maxPosition because children size will grow once wc is added. + if (!hasChild(wc)) { + ++maxPosition; + } + // Reset position based on minimum/maximum possible positions. return Math.min(Math.max(suggestedPosition, minPosition), maxPosition); } @@ -3203,25 +2599,12 @@ class Task extends WindowContainer<WindowContainer> { } } - @VisibleForTesting - boolean hasWindowsAlive() { - return getActivity(ActivityRecord::hasWindowsAlive) != null; - } - - @VisibleForTesting - boolean shouldDeferRemoval() { - if (mChildren.isEmpty()) { - // No reason to defer removal of a Task that doesn't have any child. - return false; - } - return hasWindowsAlive() && getRootTask().isAnimating(TRANSITION | CHILDREN); - } - @Override void removeImmediately() { removeImmediately("removeTask"); } + @Override void removeImmediately(String reason) { if (DEBUG_ROOT_TASK) Slog.i(TAG, "removeTask:" + reason + " removing taskId=" + mTaskId); if (mRemoving) { @@ -3355,10 +2738,14 @@ class Task extends WindowContainer<WindowContainer> { } boolean isResizeable() { + return isResizeable(/* checkPictureInPictureSupport */ true); + } + + boolean isResizeable(boolean checkPictureInPictureSupport) { final boolean forceResizable = mAtmService.mForceResizableActivities && getActivityType() == ACTIVITY_TYPE_STANDARD; return forceResizable || ActivityInfo.isResizeableMode(mResizeMode) - || mSupportsPictureInPicture; + || (mSupportsPictureInPicture && checkPictureInPictureSupport); } /** @@ -3477,6 +2864,30 @@ class Task extends WindowContainer<WindowContainer> { return; } + /** + * Account for specified insets to crop the animation bounds by to avoid the animation + * occurring over "out of bounds" regions + * + * For example this is used to make sure the tasks are cropped to be fully above the + * taskbar when animating. + * + * @param animationBounds The animations bounds to adjust to account for the custom spec insets. + */ + void adjustAnimationBoundsForTransition(Rect animationBounds) { + TaskTransitionSpec spec = mWmService.mTaskTransitionSpec; + if (spec != null) { + for (@InsetsState.InternalInsetsType int insetType : spec.animationBoundInsets) { + InsetsSourceProvider insetProvider = getDisplayContent() + .getInsetsStateController() + .getSourceProvider(insetType); + + Insets insets = insetProvider.getSource().calculateVisibleInsets( + animationBounds); + animationBounds.inset(insets); + } + } + } + void setDragResizing(boolean dragResizing, int dragResizeMode) { if (mDragResizing != dragResizing) { // No need to check if the mode is allowed if it's leaving dragResize @@ -3564,18 +2975,6 @@ class Task extends WindowContainer<WindowContainer> { mForceShowForAllUsers = forceShowForAllUsers; } - @Override - public boolean isAttached() { - final TaskDisplayArea taskDisplayArea = getDisplayArea(); - return taskDisplayArea != null && !taskDisplayArea.isRemoved(); - } - - @Override - @Nullable - TaskDisplayArea getDisplayArea() { - return (TaskDisplayArea) super.getDisplayArea(); - } - /** * When we are in a floating root task (Freeform, Pinned, ...) we calculate * insets differently. However if we are animating to the fullscreen root task @@ -3586,70 +2985,55 @@ class Task extends WindowContainer<WindowContainer> { return getWindowConfiguration().tasksAreFloating() && !mPreserveNonFloatingState; } - /** - * Returns true if the root task is translucent and can have other contents visible behind it if - * needed. A root task is considered translucent if it don't contain a visible or - * starting (about to be visible) activity that is fullscreen (opaque). - * @param starting The currently starting activity or null if there is none. - */ - @VisibleForTesting - boolean isTranslucent(ActivityRecord starting) { - if (!isAttached() || isForceHidden()) { - return true; - } - final PooledPredicate p = PooledLambda.obtainPredicate(Task::isOpaqueActivity, - PooledLambda.__(ActivityRecord.class), starting); - final ActivityRecord opaque = getActivity(p); - p.recycle(); - return opaque == null; - } - - private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) { - if (r.finishing) { - // We don't factor in finishing activities when determining translucency since - // they will be gone soon. - return false; - } - - if (!r.visibleIgnoringKeyguard && r != starting) { - // Also ignore invisible activities that are not the currently starting - // activity (about to be visible). - return false; - } - - if (r.occludesParent()) { - // Root task isn't translucent if it has at least one fullscreen activity - // that is visible. - return true; - } - return false; - } - /** Returns the top-most activity that occludes the given one, or {@code null} if none. */ @Nullable ActivityRecord getOccludingActivityAbove(ActivityRecord activity) { - final ActivityRecord top = getActivity(ActivityRecord::occludesParent, - true /* traverseTopToBottom */, activity); + final ActivityRecord top = getActivity(r -> { + if (r == activity) { + // Reached the given activity, return the activity to stop searching. + return true; + } + + if (!r.occludesParent()) { + return false; + } + + TaskFragment parent = r.getTaskFragment(); + if (parent == activity.getTaskFragment()) { + // Found it. This activity on top of the given activity on the same TaskFragment. + return true; + } + if (isSelfOrNonEmbeddedTask(parent.asTask())) { + // Found it. This activity is the direct child of a leaf Task without being + // embedded. + return true; + } + // The candidate activity is being embedded. Checking if the bounds of the containing + // TaskFragment equals to the outer TaskFragment. + TaskFragment grandParent = parent.getParent().asTaskFragment(); + while (grandParent != null) { + if (!parent.getBounds().equals(grandParent.getBounds())) { + // Not occluding the grandparent. + break; + } + if (isSelfOrNonEmbeddedTask(grandParent.asTask())) { + // Found it. The activity occludes its parent TaskFragment and the parent + // TaskFragment also occludes its parent all the way up. + return true; + } + parent = grandParent; + grandParent = parent.getParent().asTaskFragment(); + } + return false; + }); return top != activity ? top : null; } - /** Iterates through all occluded activities. */ - void forAllOccludedActivities(Consumer<ActivityRecord> handleOccludedActivity) { - if (!shouldBeVisible(null /* starting */)) { - // The root task is invisible so all activities are occluded. - forAllActivities(handleOccludedActivity); - return; - } - final ActivityRecord topOccluding = getOccludingActivityAbove(null); - if (topOccluding == null) { - // No activities are occluded. - return; + private boolean isSelfOrNonEmbeddedTask(Task task) { + if (task == this) { + return true; } - // Invoke the callback on the activities behind the top occluding activity. - forAllActivities(r -> { - handleOccludedActivity.accept(r); - return false; - }, topOccluding, false /* includeBoundary */, true /* traverseTopToBottom */); + return task != null && !task.isEmbedded(); } @Override @@ -3657,21 +3041,6 @@ class Task extends WindowContainer<WindowContainer> { return super.makeAnimationLeash().setMetadata(METADATA_TASK_ID, mTaskId); } - @Override - void resetSurfacePositionForAnimationLeash(SurfaceControl.Transaction t) { - super.resetSurfacePositionForAnimationLeash(t); - } - - @Override - Rect getAnimationBounds(int appRootTaskClipMode) { - // TODO(b/131661052): we should remove appRootTaskClipMode with hierarchical animations. - if (appRootTaskClipMode == ROOT_TASK_CLIP_BEFORE_ANIM && getRootTask() != null) { - // Using the root task bounds here effectively applies the clipping before animation. - return getRootTask().getBounds(); - } - return super.getAnimationBounds(appRootTaskClipMode); - } - boolean shouldAnimate() { /** * Animations are handled by the TaskOrganizer implementation. @@ -3701,36 +3070,11 @@ class Task extends WindowContainer<WindowContainer> { return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS); } - @Override - RemoteAnimationTarget createRemoteAnimationTarget( - RemoteAnimationController.RemoteAnimationRecord record) { - final ActivityRecord activity = getTopMostActivity(); - return activity != null ? activity.createRemoteAnimationTarget(record) : null; - } - - @Override - boolean canCreateRemoteAnimationTarget() { - return true; - } - WindowState getTopVisibleAppMainWindow() { final ActivityRecord activity = getTopVisibleActivity(); return activity != null ? activity.findMainWindow() : null; } - ActivityRecord topRunningActivity() { - return topRunningActivity(false /* focusableOnly */); - } - - ActivityRecord topRunningActivity(boolean focusableOnly) { - // Split into 2 to avoid object creation due to variable capture. - if (focusableOnly) { - return getActivity((r) -> r.canBeTopRunning() && r.isFocusable()); - } else { - return getActivity(ActivityRecord::canBeTopRunning); - } - } - ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) { final PooledPredicate p = PooledLambda.obtainPredicate(Task::isTopRunningNonDelayed , PooledLambda.__(ActivityRecord.class), notTop); @@ -3785,16 +3129,6 @@ class Task extends WindowContainer<WindowContainer> { }); } - boolean isTopActivityFocusable() { - final ActivityRecord r = topRunningActivity(); - return r != null ? r.isFocusable() - : (isFocusable() && getWindowConfiguration().canReceiveKeys()); - } - - boolean isFocusableAndVisible() { - return isTopActivityFocusable() && shouldBeVisible(null /* starting */); - } - void positionChildAtTop(ActivityRecord child) { positionChildAt(child, POSITION_TOP); } @@ -3838,14 +3172,6 @@ class Task extends WindowContainer<WindowContainer> { } @Override - boolean fillsParent() { - // From the perspective of policy, we still want to report that this task fills parent - // in fullscreen windowing mode even it doesn't match parent bounds because there will be - // letterbox around its real content. - return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds(); - } - - @Override void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) { final int count = mChildren.size(); boolean isLeafTask = true; @@ -3906,6 +3232,41 @@ class Task extends WindowContainer<WindowContainer> { return false; } + /** Iterates through all leaf task fragments and the leaf tasks. */ + void forAllLeafTasksAndLeafTaskFragments(final Consumer<TaskFragment> callback, + boolean traverseTopToBottom) { + forAllLeafTasks(task -> { + if (task.isLeafTaskFragment()) { + callback.accept(task); + return; + } + + // A leaf task that may contains both activities and task fragments. + boolean consumed = false; + if (traverseTopToBottom) { + for (int i = task.mChildren.size() - 1; i >= 0; --i) { + final WindowContainer child = task.mChildren.get(i); + if (child.asTaskFragment() != null) { + child.forAllLeafTaskFragments(callback, traverseTopToBottom); + } else if (child.asActivityRecord() != null && !consumed) { + callback.accept(task); + consumed = true; + } + } + } else { + for (int i = 0; i < task.mChildren.size(); i++) { + final WindowContainer child = task.mChildren.get(i); + if (child.asTaskFragment() != null) { + child.forAllLeafTaskFragments(callback, traverseTopToBottom); + } else if (child.asActivityRecord() != null && !consumed) { + callback.accept(task); + consumed = true; + } + } + } + }, traverseTopToBottom); + } + @Override boolean forAllRootTasks(Function<Task, Boolean> callback, boolean traverseTopToBottom) { return isRootTask() ? callback.apply(this) : false; @@ -4025,19 +3386,9 @@ class Task extends WindowContainer<WindowContainer> { @Override void dump(PrintWriter pw, String prefix, boolean dumpAll) { super.dump(pw, prefix, dumpAll); - pw.println(prefix + "bounds=" + getBounds().toShortString()); - final String doublePrefix = prefix + " "; - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowContainer<?> child = mChildren.get(i); - pw.println(prefix + "* " + child); - // Only dump non-activity because full activity info is already printed by - // RootWindowContainer#dumpActivities. - if (child.asActivityRecord() == null) { - child.dump(pw, doublePrefix, dumpAll); - } - } if (!mExitingActivities.isEmpty()) { + final String doublePrefix = prefix + " "; pw.println(); pw.println(prefix + "Exiting application tokens:"); for (int i = mExitingActivities.size() - 1; i >= 0; i--) { @@ -4076,6 +3427,9 @@ class Task extends WindowContainer<WindowContainer> { info.userId = isLeafTask() ? mUserId : mCurrentUser; info.taskId = mTaskId; info.displayId = getDisplayId(); + if (tda != null) { + info.displayAreaFeatureId = tda.mFeatureId; + } info.isRunning = getTopNonFinishingActivity() != null; final Intent baseIntent = getBaseIntent(); // Make a copy of base intent because this is like a snapshot info. @@ -4135,6 +3489,7 @@ class Task extends WindowContainer<WindowContainer> { : INVALID_TASK_ID; info.isFocused = isFocused(); info.isVisible = hasVisibleChildren(); + info.isSleeping = shouldSleepActivities(); ActivityRecord topRecord = getTopNonFinishingActivity(); info.mTopActivityLocusId = topRecord != null ? topRecord.getLocusId() : null; } @@ -4145,9 +3500,9 @@ class Task extends WindowContainer<WindowContainer> { private @Nullable PictureInPictureParams getPictureInPictureParams(Task top) { if (top == null) return null; - final ActivityRecord topVisibleActivity = top.getTopVisibleActivity(); - return (topVisibleActivity == null || topVisibleActivity.pictureInPictureArgs.empty()) - ? null : new PictureInPictureParams(topVisibleActivity.pictureInPictureArgs); + final ActivityRecord topMostActivity = top.getTopMostActivity(); + return (topMostActivity == null || topMostActivity.pictureInPictureArgs.empty()) + ? null : new PictureInPictureParams(topMostActivity.pictureInPictureArgs); } Rect getDisplayCutoutInsets() { @@ -4187,6 +3542,7 @@ class Task extends WindowContainer<WindowContainer> { final WindowState mainWindow = activity.findMainWindow(); if (mainWindow != null) { info.mainWindowLayoutParams = mainWindow.getAttrs(); + info.requestedVisibilities.set(mainWindow.getRequestedVisibilities()); } // If the developer has persist a different configuration, we need to override it to the // starting window because persisted configuration does not effect to Task. @@ -4214,184 +3570,6 @@ class Task extends WindowContainer<WindowContainer> { return this; } - /** - * Returns true if the task should be visible. - * - * @param starting The currently starting activity or null if there is none. - */ - boolean shouldBeVisible(ActivityRecord starting) { - return getVisibility(starting) != TASK_VISIBILITY_INVISIBLE; - } - - /** - * Returns true if the task should be visible. - * - * @param starting The currently starting activity or null if there is none. - */ - @TaskVisibility - int getVisibility(ActivityRecord starting) { - if (!isAttached() || isForceHidden()) { - return TASK_VISIBILITY_INVISIBLE; - } - - if (isTopActivityLaunchedBehind()) { - return TASK_VISIBILITY_VISIBLE; - } - - boolean gotRootSplitScreenTask = false; - boolean gotOpaqueSplitScreenPrimary = false; - boolean gotOpaqueSplitScreenSecondary = false; - boolean gotTranslucentFullscreen = false; - boolean gotTranslucentSplitScreenPrimary = false; - boolean gotTranslucentSplitScreenSecondary = false; - boolean shouldBeVisible = true; - - // This root task is only considered visible if all its parent root tasks are considered - // visible, so check the visibility of all ancestor root task first. - final WindowContainer parent = getParent(); - if (parent.asTask() != null) { - final int parentVisibility = parent.asTask().getVisibility(starting); - if (parentVisibility == TASK_VISIBILITY_INVISIBLE) { - // Can't be visible if parent isn't visible - return TASK_VISIBILITY_INVISIBLE; - } else if (parentVisibility == TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT) { - // Parent is behind a translucent container so the highest visibility this container - // can get is that. - gotTranslucentFullscreen = true; - } - } - - final List<Task> adjacentTasks = new ArrayList<>(); - final int windowingMode = getWindowingMode(); - final boolean isAssistantType = isActivityTypeAssistant(); - for (int i = parent.getChildCount() - 1; i >= 0; --i) { - final WindowContainer wc = parent.getChildAt(i); - final Task other = wc.asTask(); - if (other == null) continue; - - final boolean hasRunningActivities = other.topRunningActivity() != null; - if (other == this) { - // Should be visible if there is no other stack occluding it, unless it doesn't - // have any running activities, not starting one and not home stack. - shouldBeVisible = hasRunningActivities || isInTask(starting) != null - || isActivityTypeHome(); - break; - } - - if (!hasRunningActivities) { - continue; - } - - final int otherWindowingMode = other.getWindowingMode(); - - if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) { - if (other.isTranslucent(starting)) { - // Can be visible behind a translucent fullscreen stack. - gotTranslucentFullscreen = true; - continue; - } - return TASK_VISIBILITY_INVISIBLE; - } else if (otherWindowingMode == WINDOWING_MODE_MULTI_WINDOW - && other.matchParentBounds()) { - if (other.isTranslucent(starting)) { - // Can be visible behind a translucent task. - gotTranslucentFullscreen = true; - continue; - } - // Multi-window task that matches parent bounds would occlude other children. - return TASK_VISIBILITY_INVISIBLE; - } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - && !gotOpaqueSplitScreenPrimary) { - gotRootSplitScreenTask = true; - gotTranslucentSplitScreenPrimary = other.isTranslucent(starting); - gotOpaqueSplitScreenPrimary = !gotTranslucentSplitScreenPrimary; - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - && gotOpaqueSplitScreenPrimary) { - // Can not be visible behind another opaque stack in split-screen-primary mode. - return TASK_VISIBILITY_INVISIBLE; - } - } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY - && !gotOpaqueSplitScreenSecondary) { - gotRootSplitScreenTask = true; - gotTranslucentSplitScreenSecondary = other.isTranslucent(starting); - gotOpaqueSplitScreenSecondary = !gotTranslucentSplitScreenSecondary; - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY - && gotOpaqueSplitScreenSecondary) { - // Can not be visible behind another opaque stack in split-screen-secondary mode. - return TASK_VISIBILITY_INVISIBLE; - } - } - if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) { - // Can not be visible if we are in split-screen windowing mode and both halves of - // the screen are opaque. - return TASK_VISIBILITY_INVISIBLE; - } - if (isAssistantType && gotRootSplitScreenTask) { - // Assistant stack can't be visible behind split-screen. In addition to this not - // making sense, it also works around an issue here we boost the z-order of the - // assistant window surfaces in window manager whenever it is visible. - return TASK_VISIBILITY_INVISIBLE; - } - if (other.mAdjacentTask != null) { - if (adjacentTasks.contains(other.mAdjacentTask)) { - if (other.isTranslucent(starting) - || other.mAdjacentTask.isTranslucent(starting)) { - // Can be visible behind a translucent adjacent tasks. - gotTranslucentFullscreen = true; - continue; - } - // Can not be visible behind adjacent tasks. - return TASK_VISIBILITY_INVISIBLE; - } else { - adjacentTasks.add(other); - } - } - } - - if (!shouldBeVisible) { - return TASK_VISIBILITY_INVISIBLE; - } - - // Handle cases when there can be a translucent split-screen stack on top. - switch (windowingMode) { - case WINDOWING_MODE_FULLSCREEN: - if (gotTranslucentSplitScreenPrimary || gotTranslucentSplitScreenSecondary) { - // At least one of the split-screen stacks that covers this one is translucent. - // When in split mode, home task will be reparented to the secondary split while - // leaving tasks not supporting split below. Due to - // TaskDisplayArea#assignRootTaskOrdering always adjusts home surface layer to - // the bottom, this makes sure tasks not in split roots won't occlude home task - // unexpectedly. - return TASK_VISIBILITY_INVISIBLE; - } - break; - case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: - if (gotTranslucentSplitScreenPrimary) { - // Covered by translucent primary split-screen on top. - return TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; - } - break; - case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: - if (gotTranslucentSplitScreenSecondary) { - // Covered by translucent secondary split-screen on top. - return TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; - } - break; - } - - // Lastly - check if there is a translucent fullscreen stack on top. - return gotTranslucentFullscreen ? TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT - : TASK_VISIBILITY_VISIBLE; - } - - private boolean isTopActivityLaunchedBehind() { - final ActivityRecord top = topRunningActivity(); - if (top != null && top.mLaunchTaskBehind) { - return true; - } - return false; - } - ActivityRecord isInTask(ActivityRecord r) { if (r == null) { return null; @@ -4502,9 +3680,6 @@ class Task extends WindowContainer<WindowContainer> { pw.print(" isResizeable="); pw.println(isResizeable()); pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime); pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)"); - if (mForceNotOrganized) { - pw.print(prefix); pw.println("mForceNotOrganized=true"); - } } @Override @@ -4521,6 +3696,8 @@ class Task extends WindowContainer<WindowContainer> { } sb.append(" visible="); sb.append(shouldBeVisible(null /* starting */)); + sb.append(" visibleRequested="); + sb.append(isVisibleRequested()); sb.append(" mode="); sb.append(windowingModeToString(getWindowingMode())); sb.append(" translucent="); @@ -4574,7 +3751,7 @@ class Task extends WindowContainer<WindowContainer> { // Increment the total number of non-finishing activities numActivities++; - if (top == null || (top.isState(ActivityState.INITIALIZING))) { + if (top == null || (top.isState(INITIALIZING))) { top = r; // Reset the number of running activities until we hit the first non-initializing // activity @@ -4978,10 +4155,6 @@ class Task extends WindowContainer<WindowContainer> { } private boolean canBeOrganized() { - if (mForceNotOrganized || !mAtmService.mTaskOrganizerController - .isSupportedWindowingMode(getWindowingMode())) { - return false; - } // All root tasks can be organized if (isRootTask()) { return true; @@ -5134,9 +4307,8 @@ class Task extends WindowContainer<WindowContainer> { return setTaskOrganizer(null); } - final int windowingMode = getWindowingMode(); final TaskOrganizerController controller = mWmService.mAtmService.mTaskOrganizerController; - final ITaskOrganizer organizer = controller.getTaskOrganizer(windowingMode); + final ITaskOrganizer organizer = controller.getTaskOrganizer(); if (!forceUpdate && mTaskOrganizer == organizer) { return false; } @@ -5154,11 +4326,11 @@ class Task extends WindowContainer<WindowContainer> { /** * @return true if the task is currently focused. */ - boolean isFocused() { - if (mDisplayContent == null || mDisplayContent.mCurrentFocus == null) { + private boolean isFocused() { + if (mDisplayContent == null || mDisplayContent.mFocusedApp == null) { return false; } - return mDisplayContent.mCurrentFocus.getTask() == this; + return mDisplayContent.mFocusedApp.getTask() == this; } /** @@ -5212,13 +4384,12 @@ class Task extends WindowContainer<WindowContainer> { } /** - * Called on the task of a window which gained or lost focus. + * Called on the task when it gained or lost focus. * @param hasFocus */ - void onWindowFocusChanged(boolean hasFocus) { + void onAppFocusChanged(boolean hasFocus) { updateShadowsRadius(hasFocus, getSyncTransaction()); - // TODO(b/180525887): Un-comment once there is resolution on the bug. - // dispatchTaskInfoChangedIfNeeded(false /* force */); + dispatchTaskInfoChangedIfNeeded(false /* force */); } void onPictureInPictureParamsChanged() { @@ -5317,9 +4488,7 @@ class Task extends WindowContainer<WindowContainer> { return super.isAlwaysOnTop(); } - /** - * Returns whether this task is currently forced to be hidden for any reason. - */ + @Override protected boolean isForceHidden() { return mForceHiddenFlags != 0; } @@ -5435,14 +4604,15 @@ class Task extends WindowContainer<WindowContainer> { } super.setWindowingMode(windowingMode); - // Try reparent pinned activity back to its original task after onConfigurationChanged - // cascade finishes. This is done on Task level instead of - // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit PiP, - // we set final windowing mode on the ActivityRecord first and then on its Task when - // the exit PiP transition finishes. Meanwhile, the exit transition is always - // performed on its original task, reparent immediately in ActivityRecord breaks it. - if (currentMode == WINDOWING_MODE_PINNED) { - if (topActivity != null && topActivity.getLastParentBeforePip() != null) { + if (currentMode == WINDOWING_MODE_PINNED && topActivity != null) { + // Try reparent pinned activity back to its original task after + // onConfigurationChanged cascade finishes. This is done on Task level instead of + // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit + // PiP, we set final windowing mode on the ActivityRecord first and then on its + // Task when the exit PiP transition finishes. Meanwhile, the exit transition is + // always performed on its original task, reparent immediately in ActivityRecord + // breaks it. + if (topActivity.getLastParentBeforePip() != null) { // Do not reparent if the pinned task is in removal, indicated by the // force hidden flag. if (!isForceHidden()) { @@ -5455,6 +4625,11 @@ class Task extends WindowContainer<WindowContainer> { } } } + // Resume app-switches-allowed flag when exiting from pinned mode since + // it does not follow the ActivityStarter path. + if (topActivity.shouldBeVisible()) { + mAtmService.resumeAppSwitches(); + } } if (creating) { @@ -5464,7 +4639,8 @@ class Task extends WindowContainer<WindowContainer> { // From fullscreen to PiP. if (topActivity != null && currentMode == WINDOWING_MODE_FULLSCREEN - && windowingMode == WINDOWING_MODE_PINNED) { + && windowingMode == WINDOWING_MODE_PINNED + && !mTransitionController.isShellTransitionsEnabled()) { mDisplayContent.mPinnedTaskController .deferOrientationChangeForEnteringPipFromFullScreenIfNeeded(); } @@ -5472,8 +4648,10 @@ class Task extends WindowContainer<WindowContainer> { mAtmService.continueWindowLayout(); } - mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); - mRootWindowContainer.resumeFocusedTasksTopActivities(); + if (!mTaskSupervisor.isRootVisibilityUpdateDeferred()) { + mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); + mRootWindowContainer.resumeFocusedTasksTopActivities(); + } } void resumeNextFocusAfterReparent() { @@ -5498,23 +4676,14 @@ class Task extends WindowContainer<WindowContainer> { moveToFront(reason, null); } - /** - * @param reason The reason for moving the root task to the front. - * @param task If non-null, the task will be moved to the top of the root task. - */ void moveToFront(String reason, Task task) { - if (!isAttached()) { - return; - } - - final TaskDisplayArea taskDisplayArea = getDisplayArea(); - if (inSplitScreenSecondaryWindowingMode()) { // If the root task is in split-screen secondary mode, we need to make sure we move the // primary split-screen root task forward in the case it is currently behind a // fullscreen root task so both halves of the split-screen appear on-top and the // fullscreen root task isn't cutting between them. // TODO(b/70677280): This is a workaround until we can fix as part of b/70677280. + final TaskDisplayArea taskDisplayArea = getDisplayArea(); final Task topFullScreenRootTask = taskDisplayArea.getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN); if (topFullScreenRootTask != null) { @@ -5522,10 +4691,30 @@ class Task extends WindowContainer<WindowContainer> { taskDisplayArea.getRootSplitScreenPrimaryTask(); if (primarySplitScreenRootTask != null && topFullScreenRootTask.compareTo(primarySplitScreenRootTask) > 0) { - primarySplitScreenRootTask.moveToFront(reason + " splitScreenToTop"); + primarySplitScreenRootTask.moveToFrontInner(reason + " splitScreenToTop", + null /* task */); } } + } else if (mMoveAdjacentTogether && getAdjacentTaskFragment() != null) { + final Task adjacentTask = getAdjacentTaskFragment().asTask(); + if (adjacentTask != null) { + adjacentTask.moveToFrontInner(reason + " adjacentTaskToTop", null /* task */); + } } + moveToFrontInner(reason, task); + } + + /** + * @param reason The reason for moving the root task to the front. + * @param task If non-null, the task will be moved to the top of the root task. + */ + @VisibleForTesting + void moveToFrontInner(String reason, Task task) { + if (!isAttached()) { + return; + } + + final TaskDisplayArea taskDisplayArea = getDisplayArea(); if (!isActivityTypeHome() && returnsToHomeRootTask()) { // Make sure the root home task is behind this root task since that is where we @@ -5605,19 +4794,6 @@ class Task extends WindowContainer<WindowContainer> { r.completeResumeLocked(); } - void awakeFromSleepingLocked() { - if (!isLeafTask()) { - forAllLeafTasks((task) -> task.awakeFromSleepingLocked(), - true /* traverseTopToBottom */); - return; - } - - if (mPausingActivity != null) { - Slog.d(TAG, "awakeFromSleepingLocked: previously pausing activity didn't pause"); - mPausingActivity.activityPaused(true); - } - } - void checkReadyForSleep() { if (shouldSleepActivities() && goToSleepIfPossible(false /* shuttingDown */)) { mTaskSupervisor.checkReadyForSleepLocked(true /* allowDelay */); @@ -5636,316 +4812,13 @@ class Task extends WindowContainer<WindowContainer> { * the process of going to sleep (checkReadyForSleep will be called when that process finishes). */ boolean goToSleepIfPossible(boolean shuttingDown) { - if (!isLeafTask()) { - final int[] sleepInProgress = {0}; - forAllLeafTasks((t) -> { - if (!t.goToSleepIfPossible(shuttingDown)) { - sleepInProgress[0]++; - } - }, true); - return sleepInProgress[0] == 0; - } - - boolean shouldSleep = true; - if (mResumedActivity != null) { - // Still have something resumed; can't sleep until it is paused. - ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity); - if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING, - "Sleep => pause with userLeaving=false"); - - startPausingLocked(false /* userLeaving */, true /* uiSleeping */, null /* resuming */, - "sleep"); - shouldSleep = false ; - } else if (mPausingActivity != null) { - // Still waiting for something to pause; can't sleep yet. - ProtoLog.v(WM_DEBUG_STATES, "Sleep still waiting to pause %s", mPausingActivity); - shouldSleep = false; - } - - if (!shuttingDown) { - if (containsActivityFromRootTask(mTaskSupervisor.mStoppingActivities)) { - // Still need to tell some activities to stop; can't sleep yet. - ProtoLog.v(WM_DEBUG_STATES, "Sleep still need to stop %d activities", - mTaskSupervisor.mStoppingActivities.size()); - - mTaskSupervisor.scheduleIdle(); - shouldSleep = false; - } - } - - if (shouldSleep) { - ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, - !PRESERVE_WINDOWS); - } - - return shouldSleep; - } - - private boolean containsActivityFromRootTask(List<ActivityRecord> rs) { - for (ActivityRecord r : rs) { - if (r.getRootTask() == this) { - return true; - } - } - return false; - } - - final boolean startPausingLocked(boolean uiSleeping, ActivityRecord resuming, String reason) { - return startPausingLocked(mTaskSupervisor.mUserLeaving, uiSleeping, resuming, reason); - } - - /** - * Start pausing the currently resumed activity. It is an error to call this if there - * is already an activity being paused or there is no resumed activity. - * - * @param userLeaving True if this should result in an onUserLeaving to the current activity. - * @param uiSleeping True if this is happening with the user interface going to sleep (the - * screen turning off). - * @param resuming The activity we are currently trying to resume or null if this is not being - * called as part of resuming the top activity, so we shouldn't try to instigate - * a resume here if not null. - * @param reason The reason of pausing the activity. - * @return Returns true if an activity now is in the PAUSING state, and we are waiting for - * it to tell us when it is done. - */ - final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping, - ActivityRecord resuming, String reason) { - if (!isLeafTask()) { - final int[] pausing = {0}; - forAllLeafTasks((t) -> { - if (t.startPausingLocked(userLeaving, uiSleeping, resuming, reason)) { - pausing[0]++; - } - }, true /* traverseTopToBottom */); - return pausing[0] > 0; - } - - if (mPausingActivity != null) { - Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity - + " state=" + mPausingActivity.getState()); - if (!shouldSleepActivities()) { - // Avoid recursion among check for sleep and complete pause during sleeping. - // Because activity will be paused immediately after resume, just let pause - // be completed by the order of activity paused from clients. - completePauseLocked(false, resuming); - } - } - ActivityRecord prev = mResumedActivity; - - if (prev == null) { - if (resuming == null) { - Slog.wtf(TAG, "Trying to pause when nothing is resumed"); - mRootWindowContainer.resumeFocusedTasksTopActivities(); - } - return false; - } - - if (prev == resuming) { - Slog.wtf(TAG, "Trying to pause activity that is in process of being resumed"); - return false; - } - - //Trigger Activity Pause - if (mActivityTrigger != null) { - mActivityTrigger.activityPauseTrigger(prev.intent, prev.info, - prev.info.applicationInfo); - } - - if (mActivityPluginDelegate != null && getWindowingMode() != WINDOWING_MODE_UNDEFINED) { - mActivityPluginDelegate.activitySuspendNotification - (prev.info.packageName, getWindowingMode() == WINDOWING_MODE_FULLSCREEN, true); - } - ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSING: %s", prev); - mPausingActivity = prev; - mLastPausedActivity = prev; - if (prev.isNoHistory() && !mTaskSupervisor.mNoHistoryActivities.contains(prev)) { - mTaskSupervisor.mNoHistoryActivities.add(prev); - } - prev.setState(PAUSING, "startPausingLocked"); - prev.getTask().touchActiveTime(); - - mAtmService.updateCpuStats(); - - boolean pauseImmediately = false; - boolean shouldAutoPip = false; - if (resuming != null && (resuming.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0) { - // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous - // activity to be paused, while at the same time resuming the new resume activity - // only if the previous activity can't go into Pip since we want to give Pip - // activities a chance to enter Pip before resuming the next activity. - final boolean lastResumedCanPip = prev != null && prev.checkEnterPictureInPictureState( - "shouldResumeWhilePausing", userLeaving); - if (lastResumedCanPip && prev.pictureInPictureArgs.isAutoEnterEnabled()) { - shouldAutoPip = true; - } else if (!lastResumedCanPip) { - pauseImmediately = true; - } else { - // The previous activity may still enter PIP even though it did not allow auto-PIP. - } - } - - boolean didAutoPip = false; - if (prev.attachedToProcess()) { - if (shouldAutoPip) { - ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode " - + "directly: %s", prev); - - didAutoPip = mAtmService.enterPictureInPictureMode(prev, prev.pictureInPictureArgs); - } else { - schedulePauseActivity(prev, userLeaving, pauseImmediately, reason); - } - } else { - mPausingActivity = null; - mLastPausedActivity = null; - mTaskSupervisor.mNoHistoryActivities.remove(prev); - } - - // If we are not going to sleep, we want to ensure the device is - // awake until the next activity is started. - if (!uiSleeping && !mAtmService.isSleepingOrShuttingDownLocked()) { - mTaskSupervisor.acquireLaunchWakelock(); - } - - // If already entered PIP mode, no need to keep pausing. - if (mPausingActivity != null) { - // Have the window manager pause its key dispatching until the new - // activity has started. If we're pausing the activity just because - // the screen is being turned off and the UI is sleeping, don't interrupt - // key dispatch; the same activity will pick it up again on wakeup. - if (!uiSleeping) { - prev.pauseKeyDispatchingLocked(); - } else { - ProtoLog.v(WM_DEBUG_STATES, "Key dispatch not paused for screen off"); - } - - if (pauseImmediately) { - // If the caller said they don't want to wait for the pause, then complete - // the pause now. - completePauseLocked(false, resuming); - return false; - - } else { - prev.schedulePauseTimeout(); - return true; - } - - } else { - // This activity either failed to schedule the pause or it entered PIP mode, - // so just treat it as being paused now. - ProtoLog.v(WM_DEBUG_STATES, "Activity not running or entered PiP, resuming next."); - if (resuming == null) { - mRootWindowContainer.resumeFocusedTasksTopActivities(); - } - return false; - } - } - - void schedulePauseActivity(ActivityRecord prev, boolean userLeaving, - boolean pauseImmediately, String reason) { - ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev); - try { - EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev), - prev.shortComponentName, "userLeaving=" + userLeaving, reason); - - mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(), - prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving, - prev.configChangeFlags, pauseImmediately)); - } catch (Exception e) { - // Ignore exception, if process died other code will cleanup. - Slog.w(TAG, "Exception thrown during pause", e); - mPausingActivity = null; - mLastPausedActivity = null; - mTaskSupervisor.mNoHistoryActivities.remove(prev); - } - } - - @VisibleForTesting - void completePauseLocked(boolean resumeNext, ActivityRecord resuming) { - // Complete the pausing process of a pausing activity, so it doesn't make sense to - // operate on non-leaf tasks. - warnForNonLeafTask("completePauseLocked"); - - ActivityRecord prev = mPausingActivity; - ProtoLog.v(WM_DEBUG_STATES, "Complete pause: %s", prev); - - if (prev != null) { - prev.setWillCloseOrEnterPip(false); - final boolean wasStopping = prev.isState(STOPPING); - prev.setState(PAUSED, "completePausedLocked"); - if (prev.finishing) { - // We will update the activity visibility later, no need to do in - // completeFinishing(). Updating visibility here might also making the next - // activities to be resumed, and could result in wrong app transition due to - // lack of previous activity information. - ProtoLog.v(WM_DEBUG_STATES, "Executing finish of activity: %s", prev); - prev = prev.completeFinishing(false /* updateVisibility */, - "completePausedLocked"); - } else if (prev.hasProcess()) { - ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s " - + "wasStopping=%b visibleRequested=%b", prev, wasStopping, - prev.mVisibleRequested); - if (prev.deferRelaunchUntilPaused) { - // Complete the deferred relaunch that was waiting for pause to complete. - ProtoLog.v(WM_DEBUG_STATES, "Re-launching after pause: %s", prev); - prev.relaunchActivityLocked(prev.preserveWindowOnDeferredRelaunch); - } else if (wasStopping) { - // We are also stopping, the stop request must have gone soon after the pause. - // We can't clobber it, because the stop confirmation will not be handled. - // We don't need to schedule another stop, we only need to let it happen. - prev.setState(STOPPING, "completePausedLocked"); - } else if (!prev.mVisibleRequested || shouldSleepOrShutDownActivities()) { - // Clear out any deferred client hide we might currently have. - prev.setDeferHidingClient(false); - // If we were visible then resumeTopActivities will release resources before - // stopping. - prev.addToStopping(true /* scheduleIdle */, false /* idleDelayed */, - "completePauseLocked"); - } - } else { - ProtoLog.v(WM_DEBUG_STATES, "App died during pause, not stopping: %s", prev); - prev = null; - } - // It is possible the activity was freezing the screen before it was paused. - // In that case go ahead and remove the freeze this activity has on the screen - // since it is no longer visible. - if (prev != null) { - prev.stopFreezingScreenLocked(true /*force*/); - } - mPausingActivity = null; - } - - if (resumeNext) { - final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask(); - if (topRootTask != null && !topRootTask.shouldSleepOrShutDownActivities()) { - mRootWindowContainer.resumeFocusedTasksTopActivities(topRootTask, prev, null); - } else { - checkReadyForSleep(); - final ActivityRecord top = - topRootTask != null ? topRootTask.topRunningActivity() : null; - if (top == null || (prev != null && top != prev)) { - // If there are no more activities available to run, do resume anyway to start - // something. Also if the top activity on the root task is not the just paused - // activity, we need to go ahead and resume it to ensure we complete an - // in-flight app switch. - mRootWindowContainer.resumeFocusedTasksTopActivities(); - } + final int[] sleepInProgress = {0}; + forAllLeafTasksAndLeafTaskFragments(taskFragment -> { + if (!taskFragment.sleepIfPossible(shuttingDown)) { + sleepInProgress[0]++; } - } - - if (prev != null) { - prev.resumeKeyDispatchingLocked(); - } - - mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS); - - // Notify when the task stack has changed, but only if visibilities changed (not just - // focus). Also if there is an active root pinned task - we always want to notify it about - // task stack changes, because its positioning may depend on it. - if (mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause - || (getDisplayArea() != null && getDisplayArea().hasPinnedTask())) { - mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged(); - mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = false; - } + }, true /* traverseTopToBottom */); + return sleepInProgress[0] == 0; } boolean isTopRootTaskInDisplayArea() { @@ -5968,10 +4841,10 @@ class Task extends WindowContainer<WindowContainer> { * The activity is either starting or resuming. * Caller should ensure starting activity is visible. * @param preserveWindows Flag indicating whether windows should be preserved when updating - * configuration in {@link mEnsureActivitiesVisibleHelper}. + * configuration in {@link EnsureActivitiesVisibleHelper}. * @param configChanges Parts of the configuration that changed for this activity for evaluating * if the screen should be frozen as part of - * {@link mEnsureActivitiesVisibleHelper}. + * {@link EnsureActivitiesVisibleHelper}. * */ void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges, @@ -5987,25 +4860,22 @@ class Task extends WindowContainer<WindowContainer> { * The activity is either starting or resuming. * Caller should ensure starting activity is visible. * @param notifyClients Flag indicating whether the visibility updates should be sent to the - * clients in {@link mEnsureActivitiesVisibleHelper}. + * clients in {@link EnsureActivitiesVisibleHelper}. * @param preserveWindows Flag indicating whether windows should be preserved when updating - * configuration in {@link mEnsureActivitiesVisibleHelper}. + * configuration in {@link EnsureActivitiesVisibleHelper}. * @param configChanges Parts of the configuration that changed for this activity for evaluating * if the screen should be frozen as part of - * {@link mEnsureActivitiesVisibleHelper}. + * {@link EnsureActivitiesVisibleHelper}. */ // TODO: Should be re-worked based on the fact that each task as a root task in most cases. void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows, boolean notifyClients) { mTaskSupervisor.beginActivityVisibilityUpdate(); try { - forAllLeafTasks(task -> task.mEnsureActivitiesVisibleHelper.process( - starting, configChanges, preserveWindows, notifyClients), - true /* traverseTopToBottom */); - - // Notify WM shell that task visibilities may have changed - forAllTasks(task -> task.dispatchTaskInfoChangedIfNeeded(/* force */ false), - true /* traverseTopToBottom */); + forAllLeafTasks(task -> { + task.updateActivityVisibilities(starting, configChanges, preserveWindows, + notifyClients); + }, true /* traverseTopToBottom */); if (mTranslucentActivityWaiting != null && mUndrawnActivitiesBelowTopTranslucent.isEmpty()) { @@ -6076,25 +4946,6 @@ class Task extends WindowContainer<WindowContainer> { } } - /** @see ActivityRecord#cancelInitializing() */ - void cancelInitializingActivities() { - // We don't want to clear starting window for activities that aren't behind fullscreen - // activities as we need to display their starting window until they are done initializing. - checkBehindFullscreenActivity(null /* toCheck */, ActivityRecord::cancelInitializing); - } - - /** - * If an activity {@param toCheck} is given, this method returns {@code true} if the activity - * is occluded by any fullscreen activity. If there is no {@param toCheck} and the handling - * function {@param handleBehindFullscreenActivity} is given, this method will pass all occluded - * activities to the function. - */ - boolean checkBehindFullscreenActivity(ActivityRecord toCheck, - Consumer<ActivityRecord> handleBehindFullscreenActivity) { - return mCheckBehindFullscreenActivityHelper.process( - toCheck, handleBehindFullscreenActivity); - } - /** * Ensure that the top activity in the root task is resumed. * @@ -6135,7 +4986,8 @@ class Task extends WindowContainer<WindowContainer> { if (!child.isTopActivityFocusable()) { continue; } - if (child.getVisibility(null /* starting */) != TASK_VISIBILITY_VISIBLE) { + if (child.getVisibility(null /* starting */) + != TASK_FRAGMENT_VISIBILITY_VISIBLE) { break; } @@ -6182,409 +5034,25 @@ class Task extends WindowContainer<WindowContainer> { return false; } - // Find the next top-most activity to resume in this root task that is not finishing and is - // focusable. If it is not focusable, we will fall into the case below to resume the - // top activity in the next focusable task. - ActivityRecord next = topRunningActivity(true /* focusableOnly */); - - final boolean hasRunningActivity = next != null; - - // TODO: Maybe this entire condition can get removed? - if (hasRunningActivity && !isAttached()) { - return false; - } - - mRootWindowContainer.cancelInitializingActivities(); - - if (!hasRunningActivity) { - // There are no activities left in the root task, let's look somewhere else. + final ActivityRecord topActivity = topRunningActivity(true /* focusableOnly */); + if (topActivity == null) { + // There are no activities left in this task, let's look somewhere else. return resumeNextFocusableActivityWhenRootTaskIsEmpty(prev, options); } - next.delayedResume = false; - final TaskDisplayArea taskDisplayArea = getDisplayArea(); - - // If the top activity is the resumed one, nothing to do. - if (mResumedActivity == next && next.isState(RESUMED) - && taskDisplayArea.allResumedActivitiesComplete()) { - // Make sure we have executed any pending transitions, since there - // should be nothing left to do at this point. - executeAppTransition(options); - // For devices that are not in fullscreen mode (e.g. freeform windows), it's possible - // we still want to check if the visibility of other windows have changed (e.g. bringing - // a fullscreen window forward to cover another freeform activity.) - if (taskDisplayArea.inMultiWindowMode()) { - taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, - false /* preserveWindows */, true /* notifyClients */); - } - ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Top activity " - + "resumed %s", next); - return false; - } - - if (!next.canResumeByCompat()) { - return false; - } - - // If we are currently pausing an activity, then don't do anything until that is done. - final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete(); - if (!allPausedComplete) { - ProtoLog.v(WM_DEBUG_STATES, - "resumeTopActivityLocked: Skip resume: some activity pausing."); - - return false; - } - - // If we are sleeping, and there is no resumed activity, and the top activity is paused, - // well that is the state we want. - if (mLastPausedActivity == next && shouldSleepOrShutDownActivities()) { - // Make sure we have executed any pending transitions, since there - // should be nothing left to do at this point. - executeAppTransition(options); - ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Going to sleep and" - + " all paused"); - return false; - } - - // Make sure that the user who owns this activity is started. If not, - // we will just leave it as is because someone should be bringing - // another user's activities to the top of the stack. - if (!mAtmService.mAmInternal.hasStartedUserState(next.mUserId)) { - Slog.w(TAG, "Skipping resume of top activity " + next - + ": user " + next.mUserId + " is stopped"); - return false; - } - - // The activity may be waiting for stop, but that is no longer - // appropriate for it. - mTaskSupervisor.mStoppingActivities.remove(next); - - if (!next.translucentWindowLaunch) - next.launching = true; - - if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next); - - //Trigger Activity Resume - if (mActivityTrigger != null) { - mActivityTrigger.activityResumeTrigger(next.intent, next.info, - next.info.applicationInfo, - next.occludesParent()); - } - - if (mActivityPluginDelegate != null && getWindowingMode() != WINDOWING_MODE_UNDEFINED) { - mActivityPluginDelegate.activityInvokeNotification - (next.info.packageName, getWindowingMode() == WINDOWING_MODE_FULLSCREEN); - } - - mTaskSupervisor.setLaunchSource(next.info.applicationInfo.uid); - - ActivityRecord lastResumed = null; - final Task lastFocusedRootTask = taskDisplayArea.getLastFocusedRootTask(); - if (lastFocusedRootTask != null && lastFocusedRootTask != getRootTask()) { - // So, why aren't we using prev here??? See the param comment on the method. prev - // doesn't represent the last resumed activity. However, the last focus stack does if - // it isn't null. - lastResumed = lastFocusedRootTask.getResumedActivity(); - } - - boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next); - if (mResumedActivity != null) { - ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Pausing %s", mResumedActivity); - pausing |= startPausingLocked(false /* uiSleeping */, next, - "resumeTopActivityInnerLocked"); - } - if (pausing) { - ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivityLocked: Skip resume: need to" - + " start pausing"); - // At this point we want to put the upcoming activity's process - // at the top of the LRU list, since we know we will be needing it - // very soon and it would be a waste to let it get killed if it - // happens to be sitting towards the end. - if (next.attachedToProcess()) { - next.app.updateProcessInfo(false /* updateServiceConnectionActivities */, - true /* activityChange */, false /* updateOomAdj */, - false /* addPendingTopUid */); - } else if (!next.isProcessRunning()) { - // Since the start-process is asynchronous, if we already know the process of next - // activity isn't running, we can start the process earlier to save the time to wait - // for the current activity to be paused. - final boolean isTop = this == taskDisplayArea.getFocusedRootTask(); - mAtmService.startProcessAsync(next, false /* knownToBeDead */, isTop, - isTop ? "pre-top-activity" : "pre-activity"); - } - if (lastResumed != null) { - lastResumed.setWillCloseOrEnterPip(true); - } - return true; - } else if (mResumedActivity == next && next.isState(RESUMED) - && taskDisplayArea.allResumedActivitiesComplete()) { - // It is possible for the activity to be resumed when we paused back stacks above if the - // next activity doesn't have to wait for pause to complete. - // So, nothing else to-do except: - // Make sure we have executed any pending transitions, since there - // should be nothing left to do at this point. - executeAppTransition(options); - ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Top activity resumed " - + "(dontWaitForPause) %s", next); - return true; - } - - // If the most recent activity was noHistory but was only stopped rather - // than stopped+finished because the device went to sleep, we need to make - // sure to finish it as we're making a new activity topmost. - if (shouldSleepActivities()) { - mTaskSupervisor.finishNoHistoryActivitiesIfNeeded(next); - } - - if (prev != null && prev != next && next.nowVisible) { - - // The next activity is already visible, so hide the previous - // activity's windows right now so we can show the new one ASAP. - // We only do this if the previous is finishing, which should mean - // it is on top of the one being resumed so hiding it quickly - // is good. Otherwise, we want to do the normal route of allowing - // the resumed activity to be shown so we can decide if the - // previous should actually be hidden depending on whether the - // new one is found to be full-screen or not. - if (prev.finishing) { - prev.setVisibility(false); - if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, - "Not waiting for visible to hide: " + prev - + ", nowVisible=" + next.nowVisible); - } else { - if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, - "Previous already visible but still waiting to hide: " + prev - + ", nowVisible=" + next.nowVisible); - } - - } - - // Launching this app's activity, make sure the app is no longer - // considered stopped. - try { - mTaskSupervisor.getActivityMetricsLogger() - .notifyBeforePackageUnstopped(next.packageName); - mAtmService.getPackageManager().setPackageStoppedState( - next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */ - } catch (RemoteException e1) { - } catch (IllegalArgumentException e) { - Slog.w(TAG, "Failed trying to unstop package " - + next.packageName + ": " + e); - } - - // We are starting up the next activity, so tell the window manager - // that the previous one will be hidden soon. This way it can know - // to ignore it when computing the desired screen orientation. - boolean anim = true; - final DisplayContent dc = taskDisplayArea.mDisplayContent; - if (mPerf == null) { - mPerf = new BoostFramework(); - } - if (prev != null) { - if (prev.finishing) { - if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, - "Prepare close transition: prev=" + prev); - if (mTaskSupervisor.mNoAnimActivities.contains(prev)) { - anim = false; - dc.prepareAppTransition(TRANSIT_NONE); - } else { - if(prev.getTask() != next.getTask() && mPerf != null) { - mPerf.perfHint(BoostFramework.VENDOR_HINT_ANIM_BOOST, - next.packageName); - } - dc.prepareAppTransition(TRANSIT_CLOSE); - } - prev.setVisibility(false); - } else { - if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, - "Prepare open transition: prev=" + prev); - if (mTaskSupervisor.mNoAnimActivities.contains(next)) { - anim = false; - dc.prepareAppTransition(TRANSIT_NONE); - } else { - if(prev.getTask() != next.getTask() && mPerf != null) { - mPerf.perfHint(BoostFramework.VENDOR_HINT_ANIM_BOOST, - next.packageName); - } - dc.prepareAppTransition(TRANSIT_OPEN, - next.mLaunchTaskBehind ? TRANSIT_FLAG_OPEN_BEHIND : 0); - } - } - } else { - if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous"); - if (mTaskSupervisor.mNoAnimActivities.contains(next)) { - anim = false; - dc.prepareAppTransition(TRANSIT_NONE); - } else { - dc.prepareAppTransition(TRANSIT_OPEN); - } - } - - if (anim) { - next.applyOptionsAnimation(); - } else { - next.abortAndClearOptionsAnimation(); - } - - mTaskSupervisor.mNoAnimActivities.clear(); - - if (next.attachedToProcess()) { - if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resume running: " + next - + " stopped=" + next.stopped - + " visibleRequested=" + next.mVisibleRequested); - - // If the previous activity is translucent, force a visibility update of - // the next activity, so that it's added to WM's opening app list, and - // transition animation can be set up properly. - // For example, pressing Home button with a translucent activity in focus. - // Launcher is already visible in this case. If we don't add it to opening - // apps, maybeUpdateTransitToWallpaper() will fail to identify this as a - // TRANSIT_WALLPAPER_OPEN animation, and run some funny animation. - final boolean lastActivityTranslucent = lastFocusedRootTask != null - && (lastFocusedRootTask.inMultiWindowMode() - || (lastFocusedRootTask.mLastPausedActivity != null - && !lastFocusedRootTask.mLastPausedActivity.occludesParent())); - - // This activity is now becoming visible. - if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) { - next.setVisibility(true); - } - - // schedule launch ticks to collect information about slow apps. - next.startLaunchTickingLocked(); - - ActivityRecord lastResumedActivity = - lastFocusedRootTask == null ? null : lastFocusedRootTask.getResumedActivity(); - final ActivityState lastState = next.getState(); - - mAtmService.updateCpuStats(); - - ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (in existing)", next); - - next.setState(RESUMED, "resumeTopActivityInnerLocked"); - - // Have the window manager re-evaluate the orientation of - // the screen based on the new activity order. - boolean notUpdated = true; - - // Activity should also be visible if set mLaunchTaskBehind to true (see - // ActivityRecord#shouldBeVisibleIgnoringKeyguard()). - if (shouldBeVisible(next)) { - // We have special rotation behavior when here is some active activity that - // requests specific orientation or Keyguard is locked. Make sure all activity - // visibilities are set correctly as well as the transition is updated if needed - // to get the correct rotation behavior. Otherwise the following call to update - // the orientation may cause incorrect configurations delivered to client as a - // result of invisible window resize. - // TODO: Remove this once visibilities are set correctly immediately when - // starting an activity. - notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(), - true /* markFrozenIfConfigChanged */, false /* deferResume */); - } - - if (notUpdated) { - // The configuration update wasn't able to keep the existing - // instance of the activity, and instead started a new one. - // We should be all done, but let's just make sure our activity - // is still at the top and schedule another run if something - // weird happened. - ActivityRecord nextNext = topRunningActivity(); - ProtoLog.i(WM_DEBUG_STATES, "Activity config changed during resume: " - + "%s, new next: %s", next, nextNext); - if (nextNext != next) { - // Do over! - mTaskSupervisor.scheduleResumeTopActivities(); - } - if (!next.mVisibleRequested || next.stopped) { - next.setVisibility(true); - } - next.completeResumeLocked(); - return true; - } - - try { - final ClientTransaction transaction = - ClientTransaction.obtain(next.app.getThread(), next.appToken); - // Deliver all pending results. - ArrayList<ResultInfo> a = next.results; - if (a != null) { - final int N = a.size(); - if (!next.finishing && N > 0) { - if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, - "Delivering results to " + next + ": " + a); - transaction.addCallback(ActivityResultItem.obtain(a)); - } - } - - if (next.newIntents != null) { - transaction.addCallback( - NewIntentItem.obtain(next.newIntents, true /* resume */)); - } - - // Well the app will no longer be stopped. - // Clear app token stopped state in window manager if needed. - next.notifyAppResumed(next.stopped); - - EventLogTags.writeWmResumeActivity(next.mUserId, System.identityHashCode(next), - next.getTask().mTaskId, next.shortComponentName); - - mAtmService.getAppWarningsLocked().onResumeActivity(next); - next.app.setPendingUiCleanAndForceProcessStateUpTo(mAtmService.mTopProcessState); - next.abortAndClearOptionsAnimation(); - transaction.setLifecycleStateRequest( - ResumeActivityItem.obtain(next.app.getReportedProcState(), - dc.isNextTransitionForward())); - mAtmService.getLifecycleManager().scheduleTransaction(transaction); - - ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Resumed %s", next); - } catch (Exception e) { - // Whoops, need to restart this activity! - ProtoLog.v(WM_DEBUG_STATES, "Resume failed; resetting state to %s: " - + "%s", lastState, next); - next.setState(lastState, "resumeTopActivityInnerLocked"); - - // lastResumedActivity being non-null implies there is a lastStack present. - if (lastResumedActivity != null) { - lastResumedActivity.setState(RESUMED, "resumeTopActivityInnerLocked"); - } - - Slog.i(TAG, "Restarting because process died: " + next); - if (!next.hasBeenLaunched) { - next.hasBeenLaunched = true; - } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null - && lastFocusedRootTask.isTopRootTaskInDisplayArea()) { - next.showStartingWindow(false /* taskSwitch */); - } - mTaskSupervisor.startSpecificActivity(next, true, false); - return true; - } - - // From this point on, if something goes wrong there is no way - // to recover the activity. - try { - next.completeResumeLocked(); - } catch (Exception e) { - // If any exception gets thrown, toss away this - // activity and try the next one. - Slog.w(TAG, "Exception thrown during resume of " + next, e); - next.finishIfPossible("resume-exception", true /* oomAdj */); - return true; + final boolean[] resumed = new boolean[1]; + final TaskFragment topFragment = topActivity.getTaskFragment(); + resumed[0] = topFragment.resumeTopActivity(prev, options, deferPause); + forAllLeafTaskFragments(f -> { + if (topFragment == f) { + return; } - } else { - // Whoops, need to restart this activity! - if (!next.hasBeenLaunched) { - next.hasBeenLaunched = true; - } else { - if (SHOW_APP_STARTING_PREVIEW) { - next.showStartingWindow(false /* taskSwich */); - } - if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next); + if (!f.canBeResumed(null /* starting */)) { + return; } - ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Restarting %s", next); - mTaskSupervisor.startSpecificActivity(next, true, true); - } - - return true; + resumed[0] |= f.resumeTopActivity(prev, options, deferPause); + }, true); + return resumed[0]; } /** @@ -6618,7 +5086,7 @@ class Task extends WindowContainer<WindowContainer> { } void startActivityLocked(ActivityRecord r, @Nullable ActivityRecord focusedTopActivity, - boolean newTask, boolean keepCurTransition, ActivityOptions options, + boolean newTask, boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) { Task rTask = r.getTask(); final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront(); @@ -6664,7 +5132,6 @@ class Task extends WindowContainer<WindowContainer> { // Slot the activity into the history root task and proceed ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s " + "callers: %s", r, task, new RuntimeException("here").fillInStackTrace()); - task.positionChildAtTop(r); if (mActivityPluginDelegate != null) { mActivityPluginDelegate.activityInvokeNotification @@ -6730,21 +5197,18 @@ class Task extends WindowContainer<WindowContainer> { // "has the same starting icon" as the next one. This allows the // window manager to keep the previous window it had previously // created, if it still had one. - Task prevTask = r.getTask(); - ActivityRecord prev = prevTask.topActivityWithStartingWindow(); - if (prev != null) { - // We don't want to reuse the previous starting preview if: - // (1) The current activity is in a different task. - if (prev.getTask() != prevTask) { - prev = null; - } - // (2) The current activity is already displayed. - else if (prev.nowVisible) { - prev = null; - } + Task baseTask = r.getTask(); + if (baseTask.isEmbedded()) { + // If the task is embedded in a task fragment, there may have an existing + // starting window in the parent task. This allows the embedded activities + // to share the starting window and make sure that the window can have top + // z-order by transferring to the top activity. + baseTask = baseTask.getParent().asTaskFragment().getTask(); } - r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity), + final ActivityRecord prev = baseTask.getActivity( + a -> a.mStartingData != null && a.showToCurrentUser()); + r.showStartingWindow(prev, newTask, isTaskSwitch, true /* startActivity */, sourceRecord); } } else { @@ -6778,10 +5242,6 @@ class Task extends WindowContainer<WindowContainer> { return true; } - private boolean isTaskSwitch(ActivityRecord r, ActivityRecord topFocusedActivity) { - return topFocusedActivity != null && r.getTask() != topFocusedActivity.getTask(); - } - /** * Reset the task by reparenting the activities that have same affinity to the task or * reparenting the activities that have different affinityies out of the task, while these @@ -6839,7 +5299,6 @@ class Task extends WindowContainer<WindowContainer> { Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); Task finishedTask = r.getTask(); - mDisplayContent.prepareAppTransition(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED); mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED); r.finishIfPossible(reason, false /* oomAdj */); @@ -6897,18 +5356,6 @@ class Task extends WindowContainer<WindowContainer> { return true; } - /** Finish all activities in the root task without waiting. */ - void finishAllActivitiesImmediately() { - if (!hasChild()) { - removeIfPossible("finishAllActivitiesImmediately"); - return; - } - forAllActivities((r) -> { - Slog.d(TAG, "finishAllActivitiesImmediatelyLocked: finishing " + r); - r.destroyIfPossible("finishAllActivitiesImmediately"); - }); - } - /** @return true if the root task behind this one is a standard activity type. */ private boolean inFrontOfStandardRootTask() { final TaskDisplayArea taskDisplayArea = getDisplayArea(); @@ -7131,7 +5578,7 @@ class Task extends WindowContainer<WindowContainer> { // Don't refocus if invisible to current user final ActivityRecord top = tr.getTopNonFinishingActivity(); - if (top == null || !top.okToShowLocked()) { + if (top == null || !top.showToCurrentUser()) { positionChildAtTop(tr); if (top != null) { mTaskSupervisor.mRecentTasks.add(top.getTask()); @@ -7219,7 +5666,6 @@ class Task extends WindowContainer<WindowContainer> { // Skip the transition for pinned task. if (!inPinnedWindowingMode()) { - mDisplayContent.prepareAppTransition(TRANSIT_TO_BACK); mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_TO_BACK, tr); } moveToBack("moveTaskToBackLocked", tr); @@ -7245,13 +5691,6 @@ class Task extends WindowContainer<WindowContainer> { return true; } - /** - * Ensures all visible activities at or below the input activity have the right configuration. - */ - void ensureVisibleActivitiesConfiguration(ActivityRecord start, boolean preserveWindow) { - mEnsureVisibleActivitiesConfigHelper.process(start, preserveWindow); - } - // TODO: Can only be called from special methods in ActivityTaskSupervisor. // Need to consolidate those calls points into this resize method so anyone can call directly. void resize(Rect displayedBounds, boolean preserveWindows, boolean deferResume) { @@ -7305,114 +5744,32 @@ class Task extends WindowContainer<WindowContainer> { } } - /** - * Reset local parameters because an app's activity died. - * @param app The app of the activity that died. - * @return {@code true} if the process of the pausing activity is died. - */ - boolean handleAppDied(WindowProcessController app) { - warnForNonLeafTask("handleAppDied"); - boolean isPausingDied = false; - if (mPausingActivity != null && mPausingActivity.app == app) { - ProtoLog.v(WM_DEBUG_STATES, "App died while pausing: %s", - mPausingActivity); - mPausingActivity = null; - isPausingDied = true; - } - if (mLastPausedActivity != null && mLastPausedActivity.app == app) { - if (mLastPausedActivity.isNoHistory()) { - mTaskSupervisor.mNoHistoryActivities.remove(mLastPausedActivity); - } - mLastPausedActivity = null; - } - return isPausingDied; - } - boolean dump(FileDescriptor fd, PrintWriter pw, boolean dumpAll, boolean dumpClient, String dumpPackage, final boolean needSep) { - Runnable headerPrinter = () -> { - if (needSep) { - pw.println(); - } - pw.println(" RootTask #" + getRootTaskId() - + ": type=" + activityTypeToString(getActivityType()) - + " mode=" + windowingModeToString(getWindowingMode())); - pw.println(" isSleeping=" + shouldSleepActivities()); - pw.println(" mBounds=" + getRequestedOverrideBounds()); - pw.println(" mCreatedByOrganizer=" + mCreatedByOrganizer); - }; - - boolean printed = false; + return dump(" ", fd, pw, dumpAll, dumpClient, dumpPackage, needSep, null /* header */); + } - if (dumpPackage == null) { - // If we are not filtering by package, we want to print absolutely everything, - // so always print the header even if there are no tasks/activities inside. - headerPrinter.run(); - headerPrinter = null; - printed = true; + @Override + void dumpInner(String prefix, PrintWriter pw, boolean dumpAll, String dumpPackage) { + super.dumpInner(prefix, pw, dumpAll, dumpPackage); + if (mCreatedByOrganizer) { + pw.println(prefix + " mCreatedByOrganizer=true"); } - - printed |= printThisActivity(pw, getPausingActivity(), dumpPackage, false, - " mPausingActivity: ", null); - printed |= printThisActivity(pw, getResumedActivity(), dumpPackage, false, - " mResumedActivity: ", null); - if (dumpAll) { - printed |= printThisActivity(pw, mLastPausedActivity, dumpPackage, false, - " mLastPausedActivity: ", null); + if (mLastNonFullscreenBounds != null) { + pw.print(prefix); pw.print(" mLastNonFullscreenBounds="); + pw.println(mLastNonFullscreenBounds); } - - printed |= dumpActivities(fd, pw, dumpAll, dumpClient, dumpPackage, false, headerPrinter); - - return printed; - } - - private boolean dumpActivities(FileDescriptor fd, PrintWriter pw, boolean dumpAll, - boolean dumpClient, String dumpPackage, boolean needSep, Runnable header) { - if (!hasChild()) { - return false; + if (isLeafTask()) { + pw.println(prefix + " isSleeping=" + shouldSleepActivities()); + printThisActivity(pw, getTopPausingActivity(), dumpPackage, false, + prefix + " topPausingActivity=", null); + printThisActivity(pw, getTopResumedActivity(), dumpPackage, false, + prefix + " topResumedActivity=", null); + if (mMinWidth != INVALID_MIN_SIZE || mMinHeight != INVALID_MIN_SIZE) { + pw.print(prefix); pw.print(" mMinWidth="); pw.print(mMinWidth); + pw.print(" mMinHeight="); pw.println(mMinHeight); + } } - final AtomicBoolean printedHeader = new AtomicBoolean(false); - final AtomicBoolean printed = new AtomicBoolean(false); - forAllLeafTasks((task) -> { - final String prefix = " "; - Runnable headerPrinter = () -> { - printed.set(true); - if (!printedHeader.get()) { - if (needSep) { - pw.println(""); - } - if (header != null) { - header.run(); - } - printedHeader.set(true); - } - pw.print(prefix); pw.print("* "); pw.println(task); - pw.print(prefix); pw.print(" mBounds="); - pw.println(task.getRequestedOverrideBounds()); - pw.print(prefix); pw.print(" mMinWidth="); pw.print(task.mMinWidth); - pw.print(" mMinHeight="); pw.println(task.mMinHeight); - if (mLastNonFullscreenBounds != null) { - pw.print(prefix); - pw.print(" mLastNonFullscreenBounds="); - pw.println(task.mLastNonFullscreenBounds); - } - task.dump(pw, prefix + " "); - }; - if (dumpPackage == null) { - // If we are not filtering by package, we want to print absolutely everything, - // so always print the header even if there are no activities inside. - headerPrinter.run(); - headerPrinter = null; - } - final ArrayList<ActivityRecord> activities = new ArrayList<>(); - // Add activities by traversing the hierarchy from bottom to top, since activities - // are dumped in reverse order in {@link ActivityTaskSupervisor#dumpHistoryList()}. - task.forAllActivities((Consumer<ActivityRecord>) activities::add, - false /* traverseTopToBottom */); - dumpHistoryList(fd, pw, activities, prefix, "Hist", true, !dumpAll, dumpClient, - dumpPackage, false, headerPrinter, task); - }, true /* traverseTopToBottom */); - return printed.get(); } ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) { @@ -7798,15 +6155,6 @@ class Task extends WindowContainer<WindowContainer> { getDisplayContent().getPinnedTaskController().setActions(actions); } - /** Returns true if a removal action is still being deferred. */ - boolean handleCompleteDeferredRemoval() { - if (isAnimating(TRANSITION | CHILDREN)) { - return true; - } - - return super.handleCompleteDeferredRemoval(); - } - public DisplayInfo getDisplayInfo() { return mDisplayContent.getDisplayInfo(); } @@ -7822,6 +6170,7 @@ class Task extends WindowContainer<WindowContainer> { } } + @Override void executeAppTransition(ActivityOptions options) { mDisplayContent.executeAppTransition(); ActivityOptions.abort(options); @@ -7844,10 +6193,6 @@ class Task extends WindowContainer<WindowContainer> { return display != null ? display.isSleeping() : mAtmService.isSleepingLocked(); } - boolean shouldSleepOrShutDownActivities() { - return shouldSleepActivities() || mAtmService.mShuttingDown; - } - private Rect getRawBounds() { return super.getBounds(); } @@ -7866,14 +6211,12 @@ class Task extends WindowContainer<WindowContainer> { } final long token = proto.start(fieldId); - super.dumpDebug(proto, WINDOW_CONTAINER, logLevel); proto.write(TaskProto.ID, mTaskId); - proto.write(DISPLAY_ID, getDisplayId()); proto.write(ROOT_TASK_ID, getRootTaskId()); - if (mResumedActivity != null) { - mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY); + if (getTopResumedActivity() != null) { + getTopResumedActivity().writeIdentifierToProto(proto, RESUMED_ACTIVITY); } if (realActivity != null) { proto.write(REAL_ACTIVITY, realActivity.flattenToShortString()); @@ -7881,11 +6224,7 @@ class Task extends WindowContainer<WindowContainer> { if (origActivity != null) { proto.write(ORIG_ACTIVITY, origActivity.flattenToShortString()); } - proto.write(ACTIVITY_TYPE, getActivityType()); proto.write(RESIZE_MODE, mResizeMode); - proto.write(MIN_WIDTH, mMinWidth); - proto.write(MIN_HEIGHT, mMinHeight); - proto.write(FILLS_PARENT, matchParentBounds()); getRawBounds().dumpDebug(proto, BOUNDS); @@ -7902,6 +6241,8 @@ class Task extends WindowContainer<WindowContainer> { proto.write(AFFINITY, affinity); proto.write(HAS_CHILD_PIP_ACTIVITY, mChildPipActivity != null); + super.dumpDebug(proto, TASK_FRAGMENT, logLevel); + proto.end(token); } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 89db2ff83e6f..e0e287699299 100755 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -32,15 +32,12 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; -import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; +import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerService.TAG_ROOT_TASK; import static com.android.server.wm.DisplayContent.alwaysCreateRootTask; -import static com.android.server.wm.Task.ActivityState.DESTROYED; -import static com.android.server.wm.Task.ActivityState.RESUMED; -import static com.android.server.wm.Task.ActivityState.STOPPED; -import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -50,6 +47,7 @@ import android.annotation.ColorInt; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.WindowConfiguration; +import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.os.UserHandle; import android.util.IntArray; @@ -101,13 +99,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { private int mColorLayerCounter = 0; /** - * A control placed at the appropriate level for transitions to occur. - */ - private SurfaceControl mAppAnimationLayer; - private SurfaceControl mBoostedAppAnimationLayer; - private SurfaceControl mHomeAppAnimationLayer; - - /** * Given that the split-screen divider does not have an AppWindowToken, it * will have to live inside of a "NonAppWindowContainer". However, in visual Z order * it will need to be interleaved with some of our children, appearing on top of @@ -134,8 +125,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { private final ArrayList<WindowContainer> mTmpNormalChildren = new ArrayList<>(); private final ArrayList<WindowContainer> mTmpHomeChildren = new ArrayList<>(); private final IntArray mTmpNeedsZBoostIndexes = new IntArray(); - private int mTmpLayerForSplitScreenDividerAnchor; - private int mTmpLayerForAnimationLayer; private ArrayList<Task> mTmpTasks = new ArrayList<>(); @@ -159,7 +148,8 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { /** * A launch root task for activity launching with {@link FLAG_ACTIVITY_LAUNCH_ADJACENT} flag. */ - private Task mLaunchAdjacentFlagRootTask; + @VisibleForTesting + Task mLaunchAdjacentFlagRootTask; /** * A focusable root task that is purposely to be positioned at the top. Although the root @@ -490,7 +480,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Update the top resumed activity because the preferred top focusable task may be changed. mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded(); - final ActivityRecord r = child.getResumedActivity(); + final ActivityRecord r = child.getTopResumedActivity(); if (r != null && r == mRootWindowContainer.getTopResumedActivity()) { mAtmService.setResumedActivityUncheckLocked(r, "positionChildAt"); } @@ -802,10 +792,12 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } return SCREEN_ORIENTATION_UNSPECIFIED; } else { - // Apps and their containers are not allowed to specify an orientation of full screen - // tasks created by organizer. The organizer handles the orientation instead. - final Task task = getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN); - if (task != null && task.isVisible() && task.mCreatedByOrganizer) { + // Apps and their containers are not allowed to specify an orientation of non floating + // visible tasks created by organizer. The organizer handles the orientation instead. + final Task nonFloatingTopTask = + getRootTask(t -> !t.getWindowConfiguration().tasksAreFloating()); + if (nonFloatingTopTask != null && nonFloatingTopTask.mCreatedByOrganizer + && nonFloatingTopTask.isVisible()) { return SCREEN_ORIENTATION_UNSPECIFIED; } } @@ -872,36 +864,14 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { int layer = 0; // Place root home tasks to the bottom. - layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer, false /* normalRootTasks */); - // The home animation layer is between the root home tasks and the normal root tasks. - final int layerForHomeAnimationLayer = layer++; - mTmpLayerForSplitScreenDividerAnchor = layer++; - mTmpLayerForAnimationLayer = layer++; - layer = adjustRootTaskLayer(t, mTmpNormalChildren, layer, true /* normalRootTasks */); - - // The boosted animation layer is between the normal root tasks and the always on top - // root tasks. - final int layerForBoostedAnimationLayer = layer++; - adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer, false /* normalRootTasks */); - - t.setLayer(mHomeAppAnimationLayer, layerForHomeAnimationLayer); - t.setLayer(mAppAnimationLayer, mTmpLayerForAnimationLayer); - t.setLayer(mSplitScreenDividerAnchor, mTmpLayerForSplitScreenDividerAnchor); - t.setLayer(mBoostedAppAnimationLayer, layerForBoostedAnimationLayer); - } - - private int adjustNormalRootTaskLayer(WindowContainer child, int layer) { - if (child.asTask() != null && child.inSplitScreenWindowingMode()) { - // The split screen divider anchor is located above the split screen window. - mTmpLayerForSplitScreenDividerAnchor = layer++; - } - if ((child.asTask() != null && child.asTask().isAnimatingByRecents()) - || child.isAppTransitioning()) { - // The animation layer is located above the highest animating root task and no - // higher. - mTmpLayerForAnimationLayer = layer++; - } - return layer; + layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer); + layer = adjustRootTaskLayer(t, mTmpNormalChildren, layer); + // TODO(b/207185041): Remove this divider workaround after we full remove leagacy split and + // make app pair split only have single root then we can just attach the + // divider to the single root task in shell. + layer = Math.max(layer, SPLIT_DIVIDER_LAYER + 1); + adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer); + t.setLayer(mSplitScreenDividerAnchor, SPLIT_DIVIDER_LAYER); } /** @@ -910,38 +880,45 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { * normal rootTasks. * * @param startLayer The beginning layer of this group of rootTasks. - * @param normalRootTasks Set {@code true} if this group is neither home nor always on top. * @return The adjusted layer value. */ private int adjustRootTaskLayer(SurfaceControl.Transaction t, - ArrayList<WindowContainer> children, int startLayer, boolean normalRootTasks) { + ArrayList<WindowContainer> children, int startLayer) { mTmpNeedsZBoostIndexes.clear(); final int childCount = children.size(); + boolean hasAdjacentTask = false; for (int i = 0; i < childCount; i++) { final WindowContainer child = children.get(i); final TaskDisplayArea childTda = child.asTaskDisplayArea(); - - boolean childNeedsZBoost = childTda != null + final boolean childNeedsZBoost = childTda != null ? childTda.childrenNeedZBoost() : child.needsZBoost(); - if (!childNeedsZBoost) { - child.assignLayer(t, startLayer++); - if (normalRootTasks) { - startLayer = adjustNormalRootTaskLayer(child, startLayer); - } - } else { + if (childNeedsZBoost) { mTmpNeedsZBoostIndexes.add(i); + continue; + } + + final Task childTask = child.asTask(); + final boolean inAdjacentTask = childTask != null + && child.inMultiWindowMode() + && childTask.getRootTask().getAdjacentTaskFragment() != null; + + if (inAdjacentTask || child.inSplitScreenWindowingMode()) { + hasAdjacentTask = true; + } else if (hasAdjacentTask && startLayer < SPLIT_DIVIDER_LAYER) { + // Task on top of adjacent tasks should be higher than split divider layer so + // set it as start. + startLayer = SPLIT_DIVIDER_LAYER + 1; } + + child.assignLayer(t, startLayer++); } final int zBoostSize = mTmpNeedsZBoostIndexes.size(); for (int i = 0; i < zBoostSize; i++) { final WindowContainer child = children.get(mTmpNeedsZBoostIndexes.get(i)); child.assignLayer(t, startLayer++); - if (normalRootTasks) { - startLayer = adjustNormalRootTaskLayer(child, startLayer); - } } return startLayer; } @@ -955,19 +932,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } @Override - SurfaceControl getAppAnimationLayer(@AnimationLayer int animationLayer) { - switch (animationLayer) { - case ANIMATION_LAYER_BOOSTED: - return mBoostedAppAnimationLayer; - case ANIMATION_LAYER_HOME: - return mHomeAppAnimationLayer; - case ANIMATION_LAYER_STANDARD: - default: - return mAppAnimationLayer; - } - } - - @Override RemoteAnimationTarget createRemoteAnimationTarget( RemoteAnimationController.RemoteAnimationRecord record) { final ActivityRecord activity = getTopMostActivity(); @@ -987,42 +951,21 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { .setName("colorBackgroundLayer") .setCallsite("TaskDisplayArea.onParentChanged") .build(); - mAppAnimationLayer = makeChildSurface(null) - .setName("animationLayer") - .setCallsite("TaskDisplayArea.onParentChanged") - .build(); - mBoostedAppAnimationLayer = makeChildSurface(null) - .setName("boostedAnimationLayer") - .setCallsite("TaskDisplayArea.onParentChanged") - .build(); - mHomeAppAnimationLayer = makeChildSurface(null) - .setName("homeAnimationLayer") - .setCallsite("TaskDisplayArea.onParentChanged") - .build(); mSplitScreenDividerAnchor = makeChildSurface(null) .setName("splitScreenDividerAnchor") .setCallsite("TaskDisplayArea.onParentChanged") .build(); getSyncTransaction() - .show(mAppAnimationLayer) - .show(mBoostedAppAnimationLayer) - .show(mHomeAppAnimationLayer) .show(mSplitScreenDividerAnchor); }); } else { super.onParentChanged(newParent, oldParent); mWmService.mTransactionFactory.get() .remove(mColorBackgroundLayer) - .remove(mAppAnimationLayer) - .remove(mBoostedAppAnimationLayer) - .remove(mHomeAppAnimationLayer) .remove(mSplitScreenDividerAnchor) .apply(); mColorBackgroundLayer = null; - mAppAnimationLayer = null; - mBoostedAppAnimationLayer = null; - mHomeAppAnimationLayer = null; mSplitScreenDividerAnchor = null; } } @@ -1063,15 +1006,12 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { @Override void migrateToNewSurfaceControl(SurfaceControl.Transaction t) { super.migrateToNewSurfaceControl(t); - if (mAppAnimationLayer == null) { + if (mColorBackgroundLayer == null) { return; } // As TaskDisplayArea is getting a new surface, reparent and reorder the child surfaces. t.reparent(mColorBackgroundLayer, mSurfaceControl); - t.reparent(mAppAnimationLayer, mSurfaceControl); - t.reparent(mBoostedAppAnimationLayer, mSurfaceControl); - t.reparent(mHomeAppAnimationLayer, mSurfaceControl); t.reparent(mSplitScreenDividerAnchor, mSurfaceControl); reassignLayer(t); scheduleAnimation(); @@ -1160,29 +1100,27 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { return rootTask; } } else if (candidateTask != null) { - final Task rootTask = candidateTask; final int position = onTop ? POSITION_TOP : POSITION_BOTTOM; final Task launchRootTask = getLaunchRootTask(windowingMode, activityType, options, sourceTask, launchFlags); - if (launchRootTask != null) { - if (rootTask.getParent() == null) { - launchRootTask.addChild(rootTask, position); - } else if (rootTask.getParent() != launchRootTask) { - rootTask.reparent(launchRootTask, position); + if (candidateTask.getParent() == null) { + launchRootTask.addChild(candidateTask, position); + } else if (candidateTask.getParent() != launchRootTask) { + candidateTask.reparent(launchRootTask, position); } - } else if (rootTask.getDisplayArea() != this || !rootTask.isRootTask()) { - if (rootTask.getParent() == null) { - addChild(rootTask, position); + } else if (candidateTask.getDisplayArea() != this || !candidateTask.isRootTask()) { + if (candidateTask.getParent() == null) { + addChild(candidateTask, position); } else { - rootTask.reparent(this, onTop); + candidateTask.reparent(this, onTop); } } // Update windowing mode if necessary, e.g. moving a pinned task to fullscreen. if (candidateTask.getWindowingMode() != windowingMode) { candidateTask.setWindowingMode(windowingMode); } - return rootTask; + return candidateTask.getRootTask(); } return new Task.Builder(mAtmService) .setWindowingMode(windowingMode) @@ -1294,7 +1232,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { + adjacentFlagRootTask); } - if (adjacentFlagRootTask.mAdjacentTask == null) { + if (adjacentFlagRootTask.getAdjacentTaskFragment() == null) { throw new UnsupportedOperationException( "Can't set non-adjacent root as launch adjacent flag root tr=" + adjacentFlagRootTask); @@ -1332,8 +1270,8 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // If the adjacent launch is coming from the same root, launch to adjacent root instead. if (sourceTask != null && sourceTask.getRootTask().mTaskId == mLaunchAdjacentFlagRootTask.mTaskId - && mLaunchAdjacentFlagRootTask.mAdjacentTask != null) { - return mLaunchAdjacentFlagRootTask.mAdjacentTask; + && mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment() != null) { + return mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment().asTask(); } else { return mLaunchAdjacentFlagRootTask; } @@ -1342,16 +1280,22 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) { if (mLaunchRootTasks.get(i).contains(windowingMode, activityType)) { final Task launchRootTask = mLaunchRootTasks.get(i).task; - // Return the focusable root task for improving the UX with staged split screen. - final Task adjacentRootTask = launchRootTask != null - ? launchRootTask.mAdjacentTask : null; - if (adjacentRootTask != null && adjacentRootTask.isFocusedRootTaskOnDisplay()) { + final TaskFragment adjacentTaskFragment = launchRootTask != null + ? launchRootTask.getAdjacentTaskFragment() : null; + final Task adjacentRootTask = + adjacentTaskFragment != null ? adjacentTaskFragment.asTask() : null; + if (sourceTask != null && sourceTask.getRootTask() == adjacentRootTask) { return adjacentRootTask; } else { return launchRootTask; } } } + // For better split UX, If task launch by the source task which root task is created by + // organizer, it should also launch in that root too. + if (sourceTask != null && sourceTask.getRootTask().mCreatedByOrganizer) { + return sourceTask.getRootTask(); + } return null; } @@ -1436,11 +1380,11 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } // TODO(b/111541062): Move this into Task#getResumedActivity() // Check if the focused root task has the resumed activity - ActivityRecord resumedActivity = focusedRootTask.getResumedActivity(); + ActivityRecord resumedActivity = focusedRootTask.getTopResumedActivity(); if (resumedActivity == null || resumedActivity.app == null) { // If there is no registered resumed activity in the root task or it is not running - // try to use previously resumed one. - resumedActivity = focusedRootTask.getPausingActivity(); + resumedActivity = focusedRootTask.getTopPausingActivity(); if (resumedActivity == null || resumedActivity.app == null) { // If previously resumed activity doesn't work either - find the topmost running // activity that can be focused. @@ -1467,7 +1411,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Clear last paused activity if focused root task changed while sleeping, so that the // top activity of current focused task can be resumed. if (mDisplayContent.isSleeping()) { - currentFocusedTask.mLastPausedActivity = null; + currentFocusedTask.clearLastPausedActivity(); } mLastFocusedRootTask = prevFocusedTask; @@ -1488,7 +1432,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { continue; } - final ActivityRecord r = mChildren.get(i).asTask().getResumedActivity(); + final ActivityRecord r = mChildren.get(i).asTask().getTopResumedActivity(); if (r != null && !r.isState(RESUMED)) { return false; } @@ -1514,18 +1458,28 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { */ boolean pauseBackTasks(ActivityRecord resuming) { final int[] someActivityPaused = {0}; - forAllLeafTasks((task) -> { - final ActivityRecord resumedActivity = task.getResumedActivity(); - if (resumedActivity != null - && (task.getVisibility(resuming) != TASK_VISIBILITY_VISIBLE - || !task.isTopActivityFocusable())) { - ProtoLog.d(WM_DEBUG_STATES, "pauseBackTasks: task=%s " - + "mResumedActivity=%s", task, resumedActivity); - if (task.startPausingLocked(false /* uiSleeping*/, - resuming, "pauseBackTasks")) { - someActivityPaused[0]++; + forAllLeafTasks(leafTask -> { + // Check if the direct child resumed activity in the leaf task needed to be paused if + // the leaf task is not a leaf task fragment. + if (!leafTask.isLeafTaskFragment()) { + final ActivityRecord top = topRunningActivity(); + final ActivityRecord resumedActivity = leafTask.getResumedActivity(); + if (resumedActivity != null && top.getTaskFragment() != leafTask) { + // Pausing the resumed activity because it is occluded by other task fragment. + if (leafTask.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) { + someActivityPaused[0]++; + } } } + + leafTask.forAllLeafTaskFragments((taskFrag) -> { + final ActivityRecord resumedActivity = taskFrag.getResumedActivity(); + if (resumedActivity != null && !taskFrag.canBeResumed(resuming)) { + if (taskFrag.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) { + someActivityPaused[0]++; + } + } + }, true /* traverseTopToBottom */); }, true /* traverseTopToBottom */); return someActivityPaused[0] > 0; } @@ -1753,13 +1707,17 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { * Whether we can show activity requesting the given min width/height in multi window below * this {@link TaskDisplayArea}. */ - boolean supportsActivityMinWidthHeightMultiWindow(int minWidth, int minHeight) { - final int configRespectsActivityMinWidthHeightMultiWindow = - mAtmService.mRespectsActivityMinWidthHeightMultiWindow; + boolean supportsActivityMinWidthHeightMultiWindow(int minWidth, int minHeight, + @Nullable ActivityInfo activityInfo) { + if (activityInfo != null && !activityInfo.shouldCheckMinWidthHeightForMultiWindow()) { + return true; + } if (minWidth <= 0 && minHeight <= 0) { // No request min width/height. return true; } + final int configRespectsActivityMinWidthHeightMultiWindow = + mAtmService.mRespectsActivityMinWidthHeightMultiWindow; if (configRespectsActivityMinWidthHeightMultiWindow == -1) { // Device override to ignore min width/height. return true; @@ -2152,7 +2110,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { if (destroyContentOnRemoval || !task.isActivityTypeStandardOrUndefined() || task.mCreatedByOrganizer) { - task.finishAllActivitiesImmediately(); + task.remove(false /* withTransition */, "removeTaskDisplayArea"); } else { // Reparent task to corresponding launch root or display area. final WindowContainer launchRoot = diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java new file mode 100644 index 000000000000..ad51387210d9 --- /dev/null +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -0,0 +1,2541 @@ +/* + * Copyright (C) 2021 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.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.content.res.Configuration.ORIENTATION_UNDEFINED; +import static android.os.Process.INVALID_UID; +import static android.os.UserHandle.USER_NULL; +import static android.view.Display.INVALID_DISPLAY; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; +import static android.view.WindowManager.TRANSIT_NONE; +import static android.view.WindowManager.TRANSIT_OPEN; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; +import static com.android.server.wm.ActivityRecord.State.PAUSED; +import static com.android.server.wm.ActivityRecord.State.PAUSING; +import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.ActivityRecord.State.STOPPING; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; +import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity; +import static com.android.server.wm.IdentifierProto.HASH_CODE; +import static com.android.server.wm.IdentifierProto.TITLE; +import static com.android.server.wm.IdentifierProto.USER_ID; +import static com.android.server.wm.TaskFragmentProto.ACTIVITY_TYPE; +import static com.android.server.wm.TaskFragmentProto.DISPLAY_ID; +import static com.android.server.wm.TaskFragmentProto.MIN_HEIGHT; +import static com.android.server.wm.TaskFragmentProto.MIN_WIDTH; +import static com.android.server.wm.TaskFragmentProto.WINDOW_CONTAINER; +import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; +import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; +import static com.android.server.wm.WindowContainerChildProto.TASK_FRAGMENT; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityOptions; +import android.app.ResultInfo; +import android.app.WindowConfiguration; +import android.app.servertransaction.ActivityResultItem; +import android.app.servertransaction.ClientTransaction; +import android.app.servertransaction.NewIntentItem; +import android.app.servertransaction.PauseActivityItem; +import android.app.servertransaction.ResumeActivityItem; +import android.content.res.Configuration; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.util.proto.ProtoOutputStream; +import android.view.DisplayInfo; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.window.ITaskFragmentOrganizer; +import android.window.TaskFragmentInfo; +import android.window.TaskFragmentOrganizerToken; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.util.function.pooled.PooledFunction; +import com.android.internal.util.function.pooled.PooledLambda; +import com.android.internal.util.function.pooled.PooledPredicate; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * A basic container that can be used to contain activities or other {@link TaskFragment}, which + * also able to manage the activity lifecycle and updates the visibilities of the activities in it. + */ +class TaskFragment extends WindowContainer<WindowContainer> { + @IntDef(prefix = {"TASK_FRAGMENT_VISIBILITY"}, value = { + TASK_FRAGMENT_VISIBILITY_VISIBLE, + TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, + TASK_FRAGMENT_VISIBILITY_INVISIBLE, + }) + @interface TaskFragmentVisibility {} + + /** + * TaskFragment is visible. No other TaskFragment(s) on top that fully or partially occlude it. + */ + static final int TASK_FRAGMENT_VISIBILITY_VISIBLE = 0; + + /** TaskFragment is partially occluded by other translucent TaskFragment(s) on top of it. */ + static final int TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT = 1; + + /** TaskFragment is completely invisible. */ + static final int TASK_FRAGMENT_VISIBILITY_INVISIBLE = 2; + + private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskFragment" : TAG_ATM; + private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; + private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS; + private static final String TAG_TRANSITION = TAG + POSTFIX_TRANSITION; + + /** Set to false to disable the preview that is shown while a new activity is being started. */ + static final boolean SHOW_APP_STARTING_PREVIEW = true; + + /** + * Indicate that the minimal width/height should use the default value. + * + * @see #mMinWidth + * @see #mMinHeight + */ + static final int INVALID_MIN_SIZE = -1; + + final ActivityTaskManagerService mAtmService; + final ActivityTaskSupervisor mTaskSupervisor; + final RootWindowContainer mRootWindowContainer; + private final TaskFragmentOrganizerController mTaskFragmentOrganizerController; + + /** + * Minimal width of this task fragment when it's resizeable. {@link #INVALID_MIN_SIZE} means it + * should use the default minimal width. + */ + int mMinWidth; + + /** + * Minimal height of this task fragment when it's resizeable. {@link #INVALID_MIN_SIZE} means it + * should use the default minimal height. + */ + int mMinHeight; + + Dimmer mDimmer = new Dimmer(this); + + /** This task fragment will be removed when the cleanup of its children are done. */ + private boolean mIsRemovalRequested; + + /** The TaskFragment that is adjacent to this one. */ + @Nullable + private TaskFragment mAdjacentTaskFragment; + + /** + * Whether to move adjacent task fragment together when re-positioning. + * + * @see #mAdjacentTaskFragment + */ + // TODO(b/207185041): Remove this once having a single-top root for split screen. + boolean mMoveAdjacentTogether; + + /** + * Prevents duplicate calls to onTaskAppeared. + */ + boolean mTaskFragmentAppearedSent; + + /** + * The last running activity of the TaskFragment was finished due to clear task while launching + * an activity in the Task. + */ + boolean mClearedTaskForReuse; + + /** + * When we are in the process of pausing an activity, before starting the + * next one, this variable holds the activity that is currently being paused. + * + * Only set at leaf task fragments. + */ + @Nullable + private ActivityRecord mPausingActivity = null; + + /** + * This is the last activity that we put into the paused state. This is + * used to determine if we need to do an activity transition while sleeping, + * when we normally hold the top activity paused. + */ + ActivityRecord mLastPausedActivity = null; + + /** + * Current activity that is resumed, or null if there is none. + * Only set at leaf task fragments. + */ + @Nullable + private ActivityRecord mResumedActivity = null; + + /** + * This TaskFragment was created by an organizer which has the following implementations. + * <ul> + * <li>The TaskFragment won't be removed when it is empty. Removal has to be an explicit + * request from the organizer.</li> + * <li>If this fragment is a Task object then unlike other non-root tasks, it's direct + * children are visible to the organizer for ordering purposes.</li> + * <li>A TaskFragment can be created by {@link android.window.TaskFragmentOrganizer}, and + * a Task can be created by {@link android.window.TaskOrganizer}.</li> + * </ul> + */ + @VisibleForTesting + boolean mCreatedByOrganizer; + + /** Whether this TaskFragment is embedded in a task. */ + private final boolean mIsEmbedded; + + /** Organizer that organizing this TaskFragment. */ + @Nullable + private ITaskFragmentOrganizer mTaskFragmentOrganizer; + private int mTaskFragmentOrganizerUid = INVALID_UID; + private @Nullable String mTaskFragmentOrganizerProcessName; + + /** Client assigned unique token for this TaskFragment if this is created by an organizer. */ + @Nullable + private IBinder mFragmentToken; + + /** + * Whether to delay the last activity of TaskFragment being immediately removed while finishing. + * This should only be set on a embedded TaskFragment, where the organizer can have the + * opportunity to perform animations and finishing the adjacent TaskFragment. + */ + private boolean mDelayLastActivityRemoval; + + final Point mLastSurfaceSize = new Point(); + + private final Rect mTmpInsets = new Rect(); + private final Rect mTmpBounds = new Rect(); + private final Rect mTmpFullBounds = new Rect(); + private final Rect mTmpStableBounds = new Rect(); + private final Rect mTmpNonDecorBounds = new Rect(); + + private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper = + new EnsureActivitiesVisibleHelper(this); + private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper = + new EnsureVisibleActivitiesConfigHelper(); + private class EnsureVisibleActivitiesConfigHelper { + private boolean mUpdateConfig; + private boolean mPreserveWindow; + private boolean mBehindFullscreen; + + void reset(boolean preserveWindow) { + mPreserveWindow = preserveWindow; + mUpdateConfig = false; + mBehindFullscreen = false; + } + + void process(ActivityRecord start, boolean preserveWindow) { + if (start == null || !start.mVisibleRequested) { + return; + } + reset(preserveWindow); + + final PooledFunction f = PooledLambda.obtainFunction( + EnsureVisibleActivitiesConfigHelper::processActivity, this, + PooledLambda.__(ActivityRecord.class)); + forAllActivities(f, start, true /*includeBoundary*/, true /*traverseTopToBottom*/); + f.recycle(); + + if (mUpdateConfig) { + // Ensure the resumed state of the focus activity if we updated the configuration of + // any activity. + mRootWindowContainer.resumeFocusedTasksTopActivities(); + } + } + + boolean processActivity(ActivityRecord r) { + mUpdateConfig |= r.ensureActivityConfiguration(0 /*globalChanges*/, mPreserveWindow); + mBehindFullscreen |= r.occludesParent(); + return mBehindFullscreen; + } + } + + /** Creates an embedded task fragment. */ + TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken, + boolean createdByOrganizer) { + this(atmService, fragmentToken, createdByOrganizer, true /* isEmbedded */); + } + + TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken, + boolean createdByOrganizer, boolean isEmbedded) { + super(atmService.mWindowManager); + + mAtmService = atmService; + mTaskSupervisor = mAtmService.mTaskSupervisor; + mRootWindowContainer = mAtmService.mRootWindowContainer; + mCreatedByOrganizer = createdByOrganizer; + mIsEmbedded = isEmbedded; + mTaskFragmentOrganizerController = + mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController; + mFragmentToken = fragmentToken; + mRemoteToken = new RemoteToken(this); + } + + @NonNull + static TaskFragment fromTaskFragmentToken(@Nullable IBinder token, + @NonNull ActivityTaskManagerService service) { + if (token == null) return null; + return service.mWindowOrganizerController.getTaskFragment(token); + } + + void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment, boolean moveTogether) { + if (mAdjacentTaskFragment == taskFragment) { + return; + } + resetAdjacentTaskFragment(); + if (taskFragment != null) { + mAdjacentTaskFragment = taskFragment; + mMoveAdjacentTogether = moveTogether; + taskFragment.setAdjacentTaskFragment(this, moveTogether); + } + } + + private void resetAdjacentTaskFragment() { + // Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment. + if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) { + mAdjacentTaskFragment.mAdjacentTaskFragment = null; + mAdjacentTaskFragment.mDelayLastActivityRemoval = false; + mAdjacentTaskFragment.mMoveAdjacentTogether = false; + } + mAdjacentTaskFragment = null; + mDelayLastActivityRemoval = false; + mMoveAdjacentTogether = false; + } + + void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid, + @NonNull String processName) { + mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder()); + mTaskFragmentOrganizerUid = uid; + mTaskFragmentOrganizerProcessName = processName; + } + + /** Whether this TaskFragment is organized by the given {@code organizer}. */ + boolean hasTaskFragmentOrganizer(ITaskFragmentOrganizer organizer) { + return organizer != null && mTaskFragmentOrganizer != null + && organizer.asBinder().equals(mTaskFragmentOrganizer.asBinder()); + } + + TaskFragment getAdjacentTaskFragment() { + return mAdjacentTaskFragment; + } + + /** Returns the currently topmost resumed activity. */ + @Nullable + ActivityRecord getTopResumedActivity() { + final ActivityRecord taskFragResumedActivity = getResumedActivity(); + for (int i = getChildCount() - 1; i >= 0; --i) { + WindowContainer<?> child = getChildAt(i); + ActivityRecord topResumedActivity = null; + if (taskFragResumedActivity != null && child == taskFragResumedActivity) { + topResumedActivity = child.asActivityRecord(); + } else if (child.asTaskFragment() != null) { + topResumedActivity = child.asTaskFragment().getTopResumedActivity(); + } + if (topResumedActivity != null) { + return topResumedActivity; + } + } + return null; + } + + /** + * Returns the currently resumed activity in this TaskFragment's + * {@link #mChildren direct children} + */ + ActivityRecord getResumedActivity() { + return mResumedActivity; + } + + void setResumedActivity(ActivityRecord r, String reason) { + warnForNonLeafTaskFragment("setResumedActivity"); + if (mResumedActivity == r) { + return; + } + + if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) { + Slog.d(TAG, "setResumedActivity taskFrag:" + this + " + from: " + + mResumedActivity + " to:" + r + " reason:" + reason); + } + mResumedActivity = r; + mTaskSupervisor.updateTopResumedActivityIfNeeded(); + } + + @VisibleForTesting + void setPausingActivity(ActivityRecord pausing) { + mPausingActivity = pausing; + } + + /** Returns the currently topmost pausing activity. */ + @Nullable + ActivityRecord getTopPausingActivity() { + final ActivityRecord taskFragPausingActivity = getPausingActivity(); + for (int i = getChildCount() - 1; i >= 0; --i) { + WindowContainer<?> child = getChildAt(i); + ActivityRecord topPausingActivity = null; + if (taskFragPausingActivity != null && child == taskFragPausingActivity) { + topPausingActivity = child.asActivityRecord(); + } else if (child.asTaskFragment() != null) { + topPausingActivity = child.asTaskFragment().getTopPausingActivity(); + } + if (topPausingActivity != null) { + return topPausingActivity; + } + } + return null; + } + + ActivityRecord getPausingActivity() { + return mPausingActivity; + } + + int getDisplayId() { + final DisplayContent dc = getDisplayContent(); + return dc != null ? dc.mDisplayId : INVALID_DISPLAY; + } + + @Nullable + Task getTask() { + if (asTask() != null) { + return asTask(); + } + + TaskFragment parent = getParent() != null ? getParent().asTaskFragment() : null; + return parent != null ? parent.getTask() : null; + } + + @Override + @Nullable + TaskDisplayArea getDisplayArea() { + return (TaskDisplayArea) super.getDisplayArea(); + } + + @Override + public boolean isAttached() { + final TaskDisplayArea taskDisplayArea = getDisplayArea(); + return taskDisplayArea != null && !taskDisplayArea.isRemoved(); + } + + /** + * Returns the root {@link TaskFragment}, which is usually also a {@link Task}. + */ + @NonNull + TaskFragment getRootTaskFragment() { + final WindowContainer parent = getParent(); + if (parent == null) return this; + + final TaskFragment parentTaskFragment = parent.asTaskFragment(); + return parentTaskFragment == null ? this : parentTaskFragment.getRootTaskFragment(); + } + + @Nullable + Task getRootTask() { + return getRootTaskFragment().asTask(); + } + + @Override + TaskFragment asTaskFragment() { + return this; + } + + @Override + boolean isEmbedded() { + if (mIsEmbedded) { + return true; + } + final WindowContainer<?> parent = getParent(); + if (parent != null) { + final TaskFragment taskFragment = parent.asTaskFragment(); + return taskFragment != null && taskFragment.isEmbedded(); + } + return false; + } + + /** + * Returns the TaskFragment that is being organized, which could be this or the ascendant + * TaskFragment. + */ + @Nullable + TaskFragment getOrganizedTaskFragment() { + if (mTaskFragmentOrganizer != null) { + return this; + } + + TaskFragment parentTaskFragment = getParent() != null ? getParent().asTaskFragment() : null; + return parentTaskFragment != null ? parentTaskFragment.getOrganizedTaskFragment() : null; + } + + /** + * Simply check and give warning logs if this is not operated on leaf {@link TaskFragment}. + */ + private void warnForNonLeafTaskFragment(String func) { + if (!isLeafTaskFragment()) { + Slog.w(TAG, func + " on non-leaf task fragment " + this); + } + } + + boolean hasDirectChildActivities() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + if (mChildren.get(i).asActivityRecord() != null) { + return true; + } + } + return false; + } + + void cleanUpActivityReferences(@NonNull ActivityRecord r) { + if (mPausingActivity != null && mPausingActivity == r) { + mPausingActivity = null; + } + + if (mResumedActivity != null && mResumedActivity == r) { + setResumedActivity(null, "cleanUpActivityReferences"); + } + r.removeTimeouts(); + } + + /** + * Returns whether this TaskFragment is currently forced to be hidden for any reason. + */ + protected boolean isForceHidden() { + return false; + } + + boolean isLeafTaskFragment() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + if (mChildren.get(i).asTaskFragment() != null) { + return false; + } + } + return true; + } + + /** + * This should be called when an child activity changes state. This should only + * be called from + * {@link ActivityRecord#setState(ActivityRecord.State, String)} . + * @param record The {@link ActivityRecord} whose state has changed. + * @param state The new state. + * @param reason The reason for the change. + */ + void onActivityStateChanged(ActivityRecord record, ActivityRecord.State state, + String reason) { + warnForNonLeafTaskFragment("onActivityStateChanged"); + if (record == mResumedActivity && state != RESUMED) { + setResumedActivity(null, reason + " - onActivityStateChanged"); + } + + if (state == RESUMED) { + if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) { + Slog.v(TAG, "set resumed activity to:" + record + " reason:" + reason); + } + setResumedActivity(record, reason + " - onActivityStateChanged"); + if (record == mRootWindowContainer.getTopResumedActivity()) { + mAtmService.setResumedActivityUncheckLocked(record, reason); + } + mTaskSupervisor.mRecentTasks.add(record.getTask()); + } + } + + /** + * Resets local parameters because an app's activity died. + * @param app The app of the activity that died. + * @return {@code true} if the process of the pausing activity is died. + */ + boolean handleAppDied(WindowProcessController app) { + warnForNonLeafTaskFragment("handleAppDied"); + boolean isPausingDied = false; + if (mPausingActivity != null && mPausingActivity.app == app) { + ProtoLog.v(WM_DEBUG_STATES, "App died while pausing: %s", + mPausingActivity); + mPausingActivity = null; + isPausingDied = true; + } + if (mLastPausedActivity != null && mLastPausedActivity.app == app) { + mLastPausedActivity = null; + } + return isPausingDied; + } + + void awakeFromSleeping() { + if (mPausingActivity != null) { + Slog.d(TAG, "awakeFromSleeping: previously pausing activity didn't pause"); + mPausingActivity.activityPaused(true); + } + } + + /** + * Tries to put the activities in the task fragment to sleep. + * + * If the task fragment is not in a state where its activities can be put to sleep, this + * function will start any necessary actions to move the task fragment into such a state. + * It is expected that this function get called again when those actions complete. + * + * @param shuttingDown {@code true} when the called because the device is shutting down. + * @return true if the root task finished going to sleep, false if the root task only started + * the process of going to sleep (checkReadyForSleep will be called when that process finishes). + */ + boolean sleepIfPossible(boolean shuttingDown) { + boolean shouldSleep = true; + if (mResumedActivity != null) { + // Still have something resumed; can't sleep until it is paused. + ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity); + startPausing(false /* userLeaving */, true /* uiSleeping */, null /* resuming */, + "sleep"); + shouldSleep = false; + } else if (mPausingActivity != null) { + // Still waiting for something to pause; can't sleep yet. + ProtoLog.v(WM_DEBUG_STATES, "Sleep still waiting to pause %s", mPausingActivity); + shouldSleep = false; + } + + if (!shuttingDown) { + if (containsStoppingActivity()) { + // Still need to tell some activities to stop; can't sleep yet. + ProtoLog.v(WM_DEBUG_STATES, "Sleep still need to stop %d activities", + mTaskSupervisor.mStoppingActivities.size()); + + mTaskSupervisor.scheduleIdle(); + shouldSleep = false; + } + } + + if (shouldSleep) { + updateActivityVisibilities(null /* starting */, 0 /* configChanges */, + !PRESERVE_WINDOWS, true /* notifyClients */); + } + + return shouldSleep; + } + + private boolean containsStoppingActivity() { + for (int i = mTaskSupervisor.mStoppingActivities.size() - 1; i >= 0; --i) { + ActivityRecord r = mTaskSupervisor.mStoppingActivities.get(i); + if (r.getTaskFragment() == this) { + return true; + } + } + return false; + } + + /** + * Returns true if the TaskFragment is translucent and can have other contents visible behind + * it if needed. A TaskFragment is considered translucent if it don't contain a visible or + * starting (about to be visible) activity that is fullscreen (opaque). + * @param starting The currently starting activity or null if there is none. + */ + @VisibleForTesting + boolean isTranslucent(ActivityRecord starting) { + if (!isAttached() || isForceHidden()) { + return true; + } + final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity, + PooledLambda.__(ActivityRecord.class), starting); + final ActivityRecord opaque = getActivity(p); + p.recycle(); + return opaque == null; + } + + private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) { + if (r.finishing) { + // We don't factor in finishing activities when determining translucency since + // they will be gone soon. + return false; + } + + if (!r.visibleIgnoringKeyguard && r != starting) { + // Also ignore invisible activities that are not the currently starting + // activity (about to be visible). + return false; + } + + if (r.occludesParent()) { + // Root task isn't translucent if it has at least one fullscreen activity + // that is visible. + return true; + } + return false; + } + + ActivityRecord getTopNonFinishingActivity() { + return getTopNonFinishingActivity(true /* includeOverlays */); + } + + ActivityRecord getTopNonFinishingActivity(boolean includeOverlays) { + return getTopNonFinishingActivity(includeOverlays, true /* includingEmbeddedTask */); + } + + /** + * Returns the top-most non-finishing activity, even if the activity is NOT ok to show to + * the current user. + * @param includeOverlays whether the task overlay activity should be included. + * @param includingEmbeddedTask whether the activity in a task that being embedded from this + * one should be included. + * @see #topRunningActivity(boolean, boolean) + */ + ActivityRecord getTopNonFinishingActivity(boolean includeOverlays, + boolean includingEmbeddedTask) { + // Split into 4 to avoid object creation due to variable capture. + if (includeOverlays) { + if (includingEmbeddedTask) { + return getActivity((r) -> !r.finishing); + } + return getActivity((r) -> !r.finishing && r.getTask() == this.getTask()); + } + + if (includingEmbeddedTask) { + return getActivity((r) -> !r.finishing && !r.isTaskOverlay()); + } + return getActivity( + (r) -> !r.finishing && !r.isTaskOverlay() && r.getTask() == this.getTask()); + } + + ActivityRecord topRunningActivity() { + return topRunningActivity(false /* focusableOnly */); + } + + ActivityRecord topRunningActivity(boolean focusableOnly) { + return topRunningActivity(focusableOnly, true /* includingEmbeddedTask */); + } + + /** + * Returns the top-most running activity, which the activity is non-finishing and ok to show + * to the current user. + * + * @see ActivityRecord#canBeTopRunning() + */ + ActivityRecord topRunningActivity(boolean focusableOnly, boolean includingEmbeddedTask) { + // Split into 4 to avoid object creation due to variable capture. + if (focusableOnly) { + if (includingEmbeddedTask) { + return getActivity((r) -> r.canBeTopRunning() && r.isFocusable()); + } + return getActivity( + (r) -> r.canBeTopRunning() && r.isFocusable() && r.getTask() == this.getTask()); + } + + if (includingEmbeddedTask) { + return getActivity(ActivityRecord::canBeTopRunning); + } + return getActivity((r) -> r.canBeTopRunning() && r.getTask() == this.getTask()); + } + + boolean isTopActivityFocusable() { + final ActivityRecord r = topRunningActivity(); + return r != null ? r.isFocusable() + : (isFocusable() && getWindowConfiguration().canReceiveKeys()); + } + + /** + * Returns the visibility state of this TaskFragment. + * + * @param starting The currently starting activity or null if there is none. + */ + @TaskFragmentVisibility + int getVisibility(ActivityRecord starting) { + if (!isAttached() || isForceHidden()) { + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } + + if (isTopActivityLaunchedBehind()) { + return TASK_FRAGMENT_VISIBILITY_VISIBLE; + } + + boolean gotRootSplitScreenFragment = false; + boolean gotOpaqueSplitScreenPrimary = false; + boolean gotOpaqueSplitScreenSecondary = false; + boolean gotTranslucentFullscreen = false; + boolean gotTranslucentAdjacent = false; + boolean gotTranslucentSplitScreenPrimary = false; + boolean gotTranslucentSplitScreenSecondary = false; + boolean shouldBeVisible = true; + + // This TaskFragment is only considered visible if all its parent TaskFragments are + // considered visible, so check the visibility of all ancestor TaskFragment first. + final WindowContainer parent = getParent(); + if (parent.asTaskFragment() != null) { + final int parentVisibility = parent.asTaskFragment().getVisibility(starting); + if (parentVisibility == TASK_FRAGMENT_VISIBILITY_INVISIBLE) { + // Can't be visible if parent isn't visible + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } else if (parentVisibility == TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT) { + // Parent is behind a translucent container so the highest visibility this container + // can get is that. + gotTranslucentFullscreen = true; + } + } + + final List<TaskFragment> adjacentTaskFragments = new ArrayList<>(); + final int windowingMode = getWindowingMode(); + final boolean isAssistantType = isActivityTypeAssistant(); + for (int i = parent.getChildCount() - 1; i >= 0; --i) { + final WindowContainer other = parent.getChildAt(i); + if (other == null) continue; + + final boolean hasRunningActivities = hasRunningActivity(other); + if (other == this) { + if (!adjacentTaskFragments.isEmpty() && !gotTranslucentAdjacent) { + // The z-order of this TaskFragment is in middle of two adjacent TaskFragments + // and it cannot be visible if the TaskFragment on top is not translucent and + // is fully occluding this one. + for (int j = adjacentTaskFragments.size() - 1; j >= 0; --j) { + final TaskFragment taskFragment = adjacentTaskFragments.get(j); + if (!taskFragment.isTranslucent(starting) + && taskFragment.getBounds().contains(this.getBounds())) { + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } + } + } + // Should be visible if there is no other fragment occluding it, unless it doesn't + // have any running activities, not starting one and not home stack. + shouldBeVisible = hasRunningActivities + || (starting != null && starting.isDescendantOf(this)) + || isActivityTypeHome(); + break; + } + + if (!hasRunningActivities) { + continue; + } + + final int otherWindowingMode = other.getWindowingMode(); + if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) { + if (isTranslucent(other, starting)) { + // Can be visible behind a translucent fullscreen TaskFragment. + gotTranslucentFullscreen = true; + continue; + } + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } else if (otherWindowingMode == WINDOWING_MODE_MULTI_WINDOW + && other.matchParentBounds()) { + if (isTranslucent(other, starting)) { + // Can be visible behind a translucent TaskFragment. + gotTranslucentFullscreen = true; + continue; + } + // Multi-window TaskFragment that matches parent bounds would occlude other children + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + && !gotOpaqueSplitScreenPrimary) { + gotRootSplitScreenFragment = true; + gotTranslucentSplitScreenPrimary = isTranslucent(other, starting); + gotOpaqueSplitScreenPrimary = !gotTranslucentSplitScreenPrimary; + if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + && gotOpaqueSplitScreenPrimary) { + // Can't be visible behind another opaque TaskFragment in split-screen-primary. + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } + } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + && !gotOpaqueSplitScreenSecondary) { + gotRootSplitScreenFragment = true; + gotTranslucentSplitScreenSecondary = isTranslucent(other, starting); + gotOpaqueSplitScreenSecondary = !gotTranslucentSplitScreenSecondary; + if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + && gotOpaqueSplitScreenSecondary) { + // Can't be visible behind another opaque TaskFragment in split-screen-secondary + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } + } + if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) { + // Can not be visible if we are in split-screen windowing mode and both halves of + // the screen are opaque. + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } + if (isAssistantType && gotRootSplitScreenFragment) { + // Assistant TaskFragment can't be visible behind split-screen. In addition to + // this not making sense, it also works around an issue here we boost the z-order + // of the assistant window surfaces in window manager whenever it is visible. + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } + + final TaskFragment otherTaskFrag = other.asTaskFragment(); + if (otherTaskFrag != null && otherTaskFrag.mAdjacentTaskFragment != null) { + if (adjacentTaskFragments.contains(otherTaskFrag.mAdjacentTaskFragment)) { + if (otherTaskFrag.isTranslucent(starting) + || otherTaskFrag.mAdjacentTaskFragment.isTranslucent(starting)) { + // Can be visible behind a translucent adjacent TaskFragments. + gotTranslucentFullscreen = true; + gotTranslucentAdjacent = true; + continue; + } + // Can not be visible behind adjacent TaskFragments. + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } else { + adjacentTaskFragments.add(otherTaskFrag); + } + } + + } + + if (!shouldBeVisible) { + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } + + // Handle cases when there can be a translucent split-screen TaskFragment on top. + switch (windowingMode) { + case WINDOWING_MODE_FULLSCREEN: + if (gotTranslucentSplitScreenPrimary || gotTranslucentSplitScreenSecondary) { + // At least one of the split-screen TaskFragment that covers this one is + // translucent. + // When in split mode, home will be reparented to the secondary split while + // leaving TaskFragments not supporting split below. Due to + // TaskDisplayArea#assignRootTaskOrdering always adjusts home surface layer to + // the bottom, this makes sure TaskFragments not in split roots won't occlude + // home task unexpectedly. + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } + break; + case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: + if (gotTranslucentSplitScreenPrimary) { + // Covered by translucent primary split-screen on top. + return TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; + } + break; + case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: + if (gotTranslucentSplitScreenSecondary) { + // Covered by translucent secondary split-screen on top. + return TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; + } + break; + } + + // Lastly - check if there is a translucent fullscreen TaskFragment on top. + return gotTranslucentFullscreen + ? TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT + : TASK_FRAGMENT_VISIBILITY_VISIBLE; + } + + private static boolean hasRunningActivity(WindowContainer wc) { + if (wc.asTaskFragment() != null) { + return wc.asTaskFragment().topRunningActivity() != null; + } + return wc.asActivityRecord() != null && !wc.asActivityRecord().finishing; + } + + private static boolean isTranslucent(WindowContainer wc, ActivityRecord starting) { + if (wc.asTaskFragment() != null) { + return wc.asTaskFragment().isTranslucent(starting); + } else if (wc.asActivityRecord() != null) { + return !wc.asActivityRecord().occludesParent(); + } + return false; + } + + + private boolean isTopActivityLaunchedBehind() { + final ActivityRecord top = topRunningActivity(); + return top != null && top.mLaunchTaskBehind; + } + + final void updateActivityVisibilities(@Nullable ActivityRecord starting, int configChanges, + boolean preserveWindows, boolean notifyClients) { + mTaskSupervisor.beginActivityVisibilityUpdate(); + try { + mEnsureActivitiesVisibleHelper.process( + starting, configChanges, preserveWindows, notifyClients); + } finally { + mTaskSupervisor.endActivityVisibilityUpdate(); + } + } + + final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options, + boolean deferPause) { + ActivityRecord next = topRunningActivity(true /* focusableOnly */); + if (next == null || !next.canResumeByCompat()) { + return false; + } + + next.delayedResume = false; + final TaskDisplayArea taskDisplayArea = getDisplayArea(); + + // If the top activity is the resumed one, nothing to do. + if (mResumedActivity == next && next.isState(RESUMED) + && taskDisplayArea.allResumedActivitiesComplete()) { + // Make sure we have executed any pending transitions, since there + // should be nothing left to do at this point. + executeAppTransition(options); + // For devices that are not in fullscreen mode (e.g. freeform windows), it's possible + // we still want to check if the visibility of other windows have changed (e.g. bringing + // a fullscreen window forward to cover another freeform activity.) + if (taskDisplayArea.inMultiWindowMode()) { + taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, + false /* preserveWindows */, true /* notifyClients */); + } + ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Top activity " + + "resumed %s", next); + return false; + } + + // If we are currently pausing an activity, then don't do anything until that is done. + final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete(); + if (!allPausedComplete) { + ProtoLog.v(WM_DEBUG_STATES, + "resumeTopActivity: Skip resume: some activity pausing."); + return false; + } + + // If we are sleeping, and there is no resumed activity, and the top activity is paused, + // well that is the state we want. + if (mLastPausedActivity == next && shouldSleepOrShutDownActivities()) { + // Make sure we have executed any pending transitions, since there + // should be nothing left to do at this point. + executeAppTransition(options); + ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Going to sleep and" + + " all paused"); + return false; + } + + // Make sure that the user who owns this activity is started. If not, + // we will just leave it as is because someone should be bringing + // another user's activities to the top of the stack. + if (!mAtmService.mAmInternal.hasStartedUserState(next.mUserId)) { + Slog.w(TAG, "Skipping resume of top activity " + next + + ": user " + next.mUserId + " is stopped"); + return false; + } + + // The activity may be waiting for stop, but that is no longer + // appropriate for it. + mTaskSupervisor.mStoppingActivities.remove(next); + + if (!next.translucentWindowLaunch) + next.launching = true; + + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next); + + // TODO(b/223439401) adjust the value-add + // //Trigger Activity Resume + // if (mActivityTrigger != null) { + // mActivityTrigger.activityResumeTrigger(next.intent, next.info, + // next.info.applicationInfo, + // next.occludesParent()); + // } + + // if (mActivityPluginDelegate != null && getWindowingMode() != WINDOWING_MODE_UNDEFINED) { + // mActivityPluginDelegate.activityInvokeNotification + // (next.info.packageName, getWindowingMode() == WINDOWING_MODE_FULLSCREEN); + // } + + mTaskSupervisor.setLaunchSource(next.info.applicationInfo.uid); + + ActivityRecord lastResumed = null; + final Task lastFocusedRootTask = taskDisplayArea.getLastFocusedRootTask(); + if (lastFocusedRootTask != null && lastFocusedRootTask != getRootTaskFragment().asTask()) { + // So, why aren't we using prev here??? See the param comment on the method. prev + // doesn't represent the last resumed activity. However, the last focus stack does if + // it isn't null. + lastResumed = lastFocusedRootTask.getTopResumedActivity(); + } + + boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next); + if (mResumedActivity != null) { + ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Pausing %s", mResumedActivity); + pausing |= startPausing(mTaskSupervisor.mUserLeaving, false /* uiSleeping */, + next, "resumeTopActivity"); + } + if (pausing) { + ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivity: Skip resume: need to" + + " start pausing"); + // At this point we want to put the upcoming activity's process + // at the top of the LRU list, since we know we will be needing it + // very soon and it would be a waste to let it get killed if it + // happens to be sitting towards the end. + if (next.attachedToProcess()) { + next.app.updateProcessInfo(false /* updateServiceConnectionActivities */, + true /* activityChange */, false /* updateOomAdj */, + false /* addPendingTopUid */); + } else if (!next.isProcessRunning()) { + // Since the start-process is asynchronous, if we already know the process of next + // activity isn't running, we can start the process earlier to save the time to wait + // for the current activity to be paused. + final boolean isTop = this == taskDisplayArea.getFocusedRootTask(); + mAtmService.startProcessAsync(next, false /* knownToBeDead */, isTop, + isTop ? "pre-top-activity" : "pre-activity"); + } + if (lastResumed != null) { + lastResumed.setWillCloseOrEnterPip(true); + } + return true; + } else if (mResumedActivity == next && next.isState(RESUMED) + && taskDisplayArea.allResumedActivitiesComplete()) { + // It is possible for the activity to be resumed when we paused back stacks above if the + // next activity doesn't have to wait for pause to complete. + // So, nothing else to-do except: + // Make sure we have executed any pending transitions, since there + // should be nothing left to do at this point. + executeAppTransition(options); + ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Top activity resumed " + + "(dontWaitForPause) %s", next); + return true; + } + + // If the most recent activity was noHistory but was only stopped rather + // than stopped+finished because the device went to sleep, we need to make + // sure to finish it as we're making a new activity topmost. + if (shouldSleepActivities()) { + mTaskSupervisor.finishNoHistoryActivitiesIfNeeded(next); + } + + if (prev != null && prev != next && next.nowVisible) { + // The next activity is already visible, so hide the previous + // activity's windows right now so we can show the new one ASAP. + // We only do this if the previous is finishing, which should mean + // it is on top of the one being resumed so hiding it quickly + // is good. Otherwise, we want to do the normal route of allowing + // the resumed activity to be shown so we can decide if the + // previous should actually be hidden depending on whether the + // new one is found to be full-screen or not. + if (prev.finishing) { + prev.setVisibility(false); + if (DEBUG_SWITCH) { + Slog.v(TAG_SWITCH, "Not waiting for visible to hide: " + prev + + ", nowVisible=" + next.nowVisible); + } + } else { + if (DEBUG_SWITCH) { + Slog.v(TAG_SWITCH, "Previous already visible but still waiting to hide: " + prev + + ", nowVisible=" + next.nowVisible); + } + } + } + + // Launching this app's activity, make sure the app is no longer + // considered stopped. + try { + mTaskSupervisor.getActivityMetricsLogger() + .notifyBeforePackageUnstopped(next.packageName); + mAtmService.getPackageManager().setPackageStoppedState( + next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */ + } catch (RemoteException e1) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + next.packageName + ": " + e); + } + + // We are starting up the next activity, so tell the window manager + // that the previous one will be hidden soon. This way it can know + // to ignore it when computing the desired screen orientation. + boolean anim = true; + final DisplayContent dc = taskDisplayArea.mDisplayContent; + // TODO(b/223439401) adjust the value-add + // if (mPerf == null) { + // mPerf = new BoostFramework(); + // } + if (prev != null) { + if (prev.finishing) { + if (DEBUG_TRANSITION) { + Slog.v(TAG_TRANSITION, "Prepare close transition: prev=" + prev); + } + if (mTaskSupervisor.mNoAnimActivities.contains(prev)) { + anim = false; + dc.prepareAppTransition(TRANSIT_NONE); + } else { + // TODO(b/223439401) adjust the value-add + // if(prev.getTask() != next.getTask() && mPerf != null) { + // mPerf.perfHint(BoostFramework.VENDOR_HINT_ANIM_BOOST, + // next.packageName); + // } + dc.prepareAppTransition(TRANSIT_CLOSE); + } + prev.setVisibility(false); + } else { + if (DEBUG_TRANSITION) { + Slog.v(TAG_TRANSITION, "Prepare open transition: prev=" + prev); + } + if (mTaskSupervisor.mNoAnimActivities.contains(next)) { + anim = false; + dc.prepareAppTransition(TRANSIT_NONE); + } else { + // TODO(b/223439401) adjust the value-add + // if(prev.getTask() != next.getTask() && mPerf != null) { + // mPerf.perfHint(BoostFramework.VENDOR_HINT_ANIM_BOOST, + // next.packageName); + // } + dc.prepareAppTransition(TRANSIT_OPEN, + next.mLaunchTaskBehind ? TRANSIT_FLAG_OPEN_BEHIND : 0); + } + } + } else { + if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous"); + if (mTaskSupervisor.mNoAnimActivities.contains(next)) { + anim = false; + dc.prepareAppTransition(TRANSIT_NONE); + } else { + dc.prepareAppTransition(TRANSIT_OPEN); + } + } + + if (anim) { + next.applyOptionsAnimation(); + } else { + next.abortAndClearOptionsAnimation(); + } + + mTaskSupervisor.mNoAnimActivities.clear(); + + if (next.attachedToProcess()) { + if (DEBUG_SWITCH) { + Slog.v(TAG_SWITCH, "Resume running: " + next + " stopped=" + next.stopped + + " visibleRequested=" + next.mVisibleRequested); + } + + // If the previous activity is translucent, force a visibility update of + // the next activity, so that it's added to WM's opening app list, and + // transition animation can be set up properly. + // For example, pressing Home button with a translucent activity in focus. + // Launcher is already visible in this case. If we don't add it to opening + // apps, maybeUpdateTransitToWallpaper() will fail to identify this as a + // TRANSIT_WALLPAPER_OPEN animation, and run some funny animation. + final boolean lastActivityTranslucent = inMultiWindowMode() + || mLastPausedActivity != null && !mLastPausedActivity.occludesParent(); + + // This activity is now becoming visible. + if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) { + next.setVisibility(true); + } + + // schedule launch ticks to collect information about slow apps. + next.startLaunchTickingLocked(); + + ActivityRecord lastResumedActivity = + lastFocusedRootTask == null ? null + : lastFocusedRootTask.getTopResumedActivity(); + final ActivityRecord.State lastState = next.getState(); + + mAtmService.updateCpuStats(); + + ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (in existing)", next); + + next.setState(RESUMED, "resumeTopActivity"); + + // Have the window manager re-evaluate the orientation of + // the screen based on the new activity order. + boolean notUpdated = true; + + // Activity should also be visible if set mLaunchTaskBehind to true (see + // ActivityRecord#shouldBeVisibleIgnoringKeyguard()). + if (shouldBeVisible(next)) { + // We have special rotation behavior when here is some active activity that + // requests specific orientation or Keyguard is locked. Make sure all activity + // visibilities are set correctly as well as the transition is updated if needed + // to get the correct rotation behavior. Otherwise the following call to update + // the orientation may cause incorrect configurations delivered to client as a + // result of invisible window resize. + // TODO: Remove this once visibilities are set correctly immediately when + // starting an activity. + notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(), + true /* markFrozenIfConfigChanged */, false /* deferResume */); + } + + if (notUpdated) { + // The configuration update wasn't able to keep the existing + // instance of the activity, and instead started a new one. + // We should be all done, but let's just make sure our activity + // is still at the top and schedule another run if something + // weird happened. + ActivityRecord nextNext = topRunningActivity(); + ProtoLog.i(WM_DEBUG_STATES, "Activity config changed during resume: " + + "%s, new next: %s", next, nextNext); + if (nextNext != next) { + // Do over! + mTaskSupervisor.scheduleResumeTopActivities(); + } + if (!next.mVisibleRequested || next.stopped) { + next.setVisibility(true); + } + next.completeResumeLocked(); + return true; + } + + try { + final ClientTransaction transaction = + ClientTransaction.obtain(next.app.getThread(), next.appToken); + // Deliver all pending results. + ArrayList<ResultInfo> a = next.results; + if (a != null) { + final int size = a.size(); + if (!next.finishing && size > 0) { + if (DEBUG_RESULTS) { + Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a); + } + transaction.addCallback(ActivityResultItem.obtain(a)); + } + } + + if (next.newIntents != null) { + transaction.addCallback( + NewIntentItem.obtain(next.newIntents, true /* resume */)); + } + + // Well the app will no longer be stopped. + // Clear app token stopped state in window manager if needed. + next.notifyAppResumed(next.stopped); + + EventLogTags.writeWmResumeActivity(next.mUserId, System.identityHashCode(next), + next.getTask().mTaskId, next.shortComponentName); + + mAtmService.getAppWarningsLocked().onResumeActivity(next); + next.app.setPendingUiCleanAndForceProcessStateUpTo(mAtmService.mTopProcessState); + next.abortAndClearOptionsAnimation(); + transaction.setLifecycleStateRequest( + ResumeActivityItem.obtain(next.app.getReportedProcState(), + dc.isNextTransitionForward())); + mAtmService.getLifecycleManager().scheduleTransaction(transaction); + + ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next); + } catch (Exception e) { + // Whoops, need to restart this activity! + ProtoLog.v(WM_DEBUG_STATES, "Resume failed; resetting state to %s: " + + "%s", lastState, next); + next.setState(lastState, "resumeTopActivityInnerLocked"); + + // lastResumedActivity being non-null implies there is a lastStack present. + if (lastResumedActivity != null) { + lastResumedActivity.setState(RESUMED, "resumeTopActivityInnerLocked"); + } + + Slog.i(TAG, "Restarting because process died: " + next); + if (!next.hasBeenLaunched) { + next.hasBeenLaunched = true; + } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null + && lastFocusedRootTask.isTopRootTaskInDisplayArea()) { + next.showStartingWindow(false /* taskSwitch */); + } + mTaskSupervisor.startSpecificActivity(next, true, false); + return true; + } + + // From this point on, if something goes wrong there is no way + // to recover the activity. + try { + next.completeResumeLocked(); + } catch (Exception e) { + // If any exception gets thrown, toss away this + // activity and try the next one. + Slog.w(TAG, "Exception thrown during resume of " + next, e); + next.finishIfPossible("resume-exception", true /* oomAdj */); + return true; + } + } else { + // Whoops, need to restart this activity! + if (!next.hasBeenLaunched) { + next.hasBeenLaunched = true; + } else { + if (SHOW_APP_STARTING_PREVIEW) { + next.showStartingWindow(false /* taskSwich */); + } + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next); + } + ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Restarting %s", next); + mTaskSupervisor.startSpecificActivity(next, true, true); + } + + return true; + } + + boolean shouldSleepOrShutDownActivities() { + return shouldSleepActivities() || mAtmService.mShuttingDown; + } + + /** + * Returns true if the TaskFragment should be visible. + * + * @param starting The currently starting activity or null if there is none. + */ + boolean shouldBeVisible(ActivityRecord starting) { + return getVisibility(starting) != TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } + + /** + * Returns {@code true} is the activity in this TaskFragment can be resumed. + * + * @param starting The currently starting activity or {@code null} if there is none. + */ + boolean canBeResumed(@Nullable ActivityRecord starting) { + // No need to resume activity in TaskFragment that is not visible. + return isTopActivityFocusable() + && getVisibility(starting) == TASK_FRAGMENT_VISIBILITY_VISIBLE; + } + + boolean isFocusableAndVisible() { + return isTopActivityFocusable() && shouldBeVisible(null /* starting */); + } + + final boolean startPausing(boolean uiSleeping, ActivityRecord resuming, String reason) { + return startPausing(mTaskSupervisor.mUserLeaving, uiSleeping, resuming, reason); + } + + /** + * Start pausing the currently resumed activity. It is an error to call this if there + * is already an activity being paused or there is no resumed activity. + * + * @param userLeaving True if this should result in an onUserLeaving to the current activity. + * @param uiSleeping True if this is happening with the user interface going to sleep (the + * screen turning off). + * @param resuming The activity we are currently trying to resume or null if this is not being + * called as part of resuming the top activity, so we shouldn't try to instigate + * a resume here if not null. + * @param reason The reason of pausing the activity. + * @return Returns true if an activity now is in the PAUSING state, and we are waiting for + * it to tell us when it is done. + */ + boolean startPausing(boolean userLeaving, boolean uiSleeping, ActivityRecord resuming, + String reason) { + if (!hasDirectChildActivities()) { + return false; + } + + ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag =%s " + "mResumedActivity=%s", this, + mResumedActivity); + + if (mPausingActivity != null) { + Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity + + " state=" + mPausingActivity.getState()); + if (!shouldSleepActivities()) { + // Avoid recursion among check for sleep and complete pause during sleeping. + // Because activity will be paused immediately after resume, just let pause + // be completed by the order of activity paused from clients. + completePause(false, resuming); + } + } + ActivityRecord prev = mResumedActivity; + + if (prev == null) { + if (resuming == null) { + Slog.wtf(TAG, "Trying to pause when nothing is resumed"); + mRootWindowContainer.resumeFocusedTasksTopActivities(); + } + return false; + } + + if (prev == resuming) { + Slog.wtf(TAG, "Trying to pause activity that is in process of being resumed"); + return false; + } + + // TODO(b/223439401) adjust the value-add + // //Trigger Activity Pause + // if (mActivityTrigger != null) { + // mActivityTrigger.activityPauseTrigger(prev.intent, prev.info, + // prev.info.applicationInfo); + // } + + // if (mActivityPluginDelegate != null && getWindowingMode() != WINDOWING_MODE_UNDEFINED) { + // mActivityPluginDelegate.activitySuspendNotification + // (prev.info.packageName, getWindowingMode() == WINDOWING_MODE_FULLSCREEN, true); + // } + ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSING: %s", prev); + mPausingActivity = prev; + mLastPausedActivity = prev; + if (!prev.finishing && prev.isNoHistory() + && !mTaskSupervisor.mNoHistoryActivities.contains(prev)) { + mTaskSupervisor.mNoHistoryActivities.add(prev); + } + prev.setState(PAUSING, "startPausingLocked"); + prev.getTask().touchActiveTime(); + + mAtmService.updateCpuStats(); + + boolean pauseImmediately = false; + boolean shouldAutoPip = false; + if (resuming != null) { + // Resuming the new resume activity only if the previous activity can't go into Pip + // since we want to give Pip activities a chance to enter Pip before resuming the + // next activity. + final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState( + "shouldAutoPipWhilePausing", userLeaving); + if (lastResumedCanPip && prev.pictureInPictureArgs.isAutoEnterEnabled()) { + shouldAutoPip = true; + } else if (!lastResumedCanPip) { + // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous + // activity to be paused. + pauseImmediately = (resuming.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0; + } else { + // The previous activity may still enter PIP even though it did not allow auto-PIP. + } + } + + if (prev.attachedToProcess()) { + if (shouldAutoPip) { + boolean didAutoPip = mAtmService.enterPictureInPictureMode( + prev, prev.pictureInPictureArgs); + ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode " + + "directly: %s, didAutoPip: %b", prev, didAutoPip); + } else { + schedulePauseActivity(prev, userLeaving, pauseImmediately, reason); + } + } else { + mPausingActivity = null; + mLastPausedActivity = null; + mTaskSupervisor.mNoHistoryActivities.remove(prev); + } + + // If we are not going to sleep, we want to ensure the device is + // awake until the next activity is started. + if (!uiSleeping && !mAtmService.isSleepingOrShuttingDownLocked()) { + mTaskSupervisor.acquireLaunchWakelock(); + } + + // If already entered PIP mode, no need to keep pausing. + if (mPausingActivity != null) { + // Have the window manager pause its key dispatching until the new + // activity has started. If we're pausing the activity just because + // the screen is being turned off and the UI is sleeping, don't interrupt + // key dispatch; the same activity will pick it up again on wakeup. + if (!uiSleeping) { + prev.pauseKeyDispatchingLocked(); + } else { + ProtoLog.v(WM_DEBUG_STATES, "Key dispatch not paused for screen off"); + } + + if (pauseImmediately) { + // If the caller said they don't want to wait for the pause, then complete + // the pause now. + completePause(false, resuming); + return false; + + } else { + prev.schedulePauseTimeout(); + // Unset readiness since we now need to wait until this pause is complete. + mTransitionController.setReady(this, false /* ready */); + return true; + } + + } else { + // This activity either failed to schedule the pause or it entered PIP mode, + // so just treat it as being paused now. + ProtoLog.v(WM_DEBUG_STATES, "Activity not running or entered PiP, resuming next."); + if (resuming == null) { + mRootWindowContainer.resumeFocusedTasksTopActivities(); + } + return false; + } + } + + void schedulePauseActivity(ActivityRecord prev, boolean userLeaving, + boolean pauseImmediately, String reason) { + ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev); + try { + EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev), + prev.shortComponentName, "userLeaving=" + userLeaving, reason); + + mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(), + prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving, + prev.configChangeFlags, pauseImmediately)); + } catch (Exception e) { + // Ignore exception, if process died other code will cleanup. + Slog.w(TAG, "Exception thrown during pause", e); + mPausingActivity = null; + mLastPausedActivity = null; + mTaskSupervisor.mNoHistoryActivities.remove(prev); + } + } + + @VisibleForTesting + void completePause(boolean resumeNext, ActivityRecord resuming) { + // Complete the pausing process of a pausing activity, so it doesn't make sense to + // operate on non-leaf tasks. + // warnForNonLeafTask("completePauseLocked"); + + ActivityRecord prev = mPausingActivity; + ProtoLog.v(WM_DEBUG_STATES, "Complete pause: %s", prev); + + if (prev != null) { + prev.setWillCloseOrEnterPip(false); + final boolean wasStopping = prev.isState(STOPPING); + prev.setState(PAUSED, "completePausedLocked"); + if (prev.finishing) { + // We will update the activity visibility later, no need to do in + // completeFinishing(). Updating visibility here might also making the next + // activities to be resumed, and could result in wrong app transition due to + // lack of previous activity information. + ProtoLog.v(WM_DEBUG_STATES, "Executing finish of activity: %s", prev); + prev = prev.completeFinishing(false /* updateVisibility */, + "completePausedLocked"); + } else if (prev.hasProcess()) { + ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s " + + "wasStopping=%b visibleRequested=%b", prev, wasStopping, + prev.mVisibleRequested); + if (prev.deferRelaunchUntilPaused) { + // Complete the deferred relaunch that was waiting for pause to complete. + ProtoLog.v(WM_DEBUG_STATES, "Re-launching after pause: %s", prev); + prev.relaunchActivityLocked(prev.preserveWindowOnDeferredRelaunch); + } else if (wasStopping) { + // We are also stopping, the stop request must have gone soon after the pause. + // We can't clobber it, because the stop confirmation will not be handled. + // We don't need to schedule another stop, we only need to let it happen. + prev.setState(STOPPING, "completePausedLocked"); + } else if (!prev.mVisibleRequested || shouldSleepOrShutDownActivities()) { + // Clear out any deferred client hide we might currently have. + prev.setDeferHidingClient(false); + // If we were visible then resumeTopActivities will release resources before + // stopping. + prev.addToStopping(true /* scheduleIdle */, false /* idleDelayed */, + "completePauseLocked"); + } + } else { + ProtoLog.v(WM_DEBUG_STATES, "App died during pause, not stopping: %s", prev); + prev = null; + } + // It is possible the activity was freezing the screen before it was paused. + // In that case go ahead and remove the freeze this activity has on the screen + // since it is no longer visible. + if (prev != null) { + prev.stopFreezingScreenLocked(true /*force*/); + } + mPausingActivity = null; + } + + if (resumeNext) { + final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask(); + if (topRootTask != null && !topRootTask.shouldSleepOrShutDownActivities()) { + mRootWindowContainer.resumeFocusedTasksTopActivities(topRootTask, prev, + null /* targetOptions */); + } else { + // checkReadyForSleep(); + final ActivityRecord top = + topRootTask != null ? topRootTask.topRunningActivity() : null; + if (top == null || (prev != null && top != prev)) { + // If there are no more activities available to run, do resume anyway to start + // something. Also if the top activity on the root task is not the just paused + // activity, we need to go ahead and resume it to ensure we complete an + // in-flight app switch. + mRootWindowContainer.resumeFocusedTasksTopActivities(); + } + } + } + + if (prev != null) { + prev.resumeKeyDispatchingLocked(); + } + + mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS); + + // Notify when the task stack has changed, but only if visibilities changed (not just + // focus). Also if there is an active root pinned task - we always want to notify it about + // task stack changes, because its positioning may depend on it. + if (mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause + || (getDisplayArea() != null && getDisplayArea().hasPinnedTask())) { + mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged(); + mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = false; + } + } + + @Override + void forAllTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) { + super.forAllTaskFragments(callback, traverseTopToBottom); + callback.accept(this); + } + + @Override + void forAllLeafTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) { + final int count = mChildren.size(); + boolean isLeafTaskFrag = true; + if (traverseTopToBottom) { + for (int i = count - 1; i >= 0; --i) { + final TaskFragment child = mChildren.get(i).asTaskFragment(); + if (child != null) { + isLeafTaskFrag = false; + child.forAllLeafTaskFragments(callback, traverseTopToBottom); + } + } + } else { + for (int i = 0; i < count; i++) { + final TaskFragment child = mChildren.get(i).asTaskFragment(); + if (child != null) { + isLeafTaskFrag = false; + child.forAllLeafTaskFragments(callback, traverseTopToBottom); + } + } + } + if (isLeafTaskFrag) callback.accept(this); + } + + @Override + boolean forAllLeafTaskFragments(Function<TaskFragment, Boolean> callback) { + boolean isLeafTaskFrag = true; + for (int i = mChildren.size() - 1; i >= 0; --i) { + final TaskFragment child = mChildren.get(i).asTaskFragment(); + if (child != null) { + isLeafTaskFrag = false; + if (child.forAllLeafTaskFragments(callback)) { + return true; + } + } + } + if (isLeafTaskFrag) { + return callback.apply(this); + } + return false; + } + + void addChild(ActivityRecord r) { + addChild(r, POSITION_TOP); + } + + @Override + void addChild(WindowContainer child, int index) { + mClearedTaskForReuse = false; + + boolean isAddingActivity = child.asActivityRecord() != null; + final Task task = isAddingActivity ? getTask() : null; + + // If this task had any activity before we added this one. + boolean taskHadActivity = task != null && task.getActivity(Objects::nonNull) != null; + // getActivityType() looks at the top child, so we need to read the type before adding + // a new child in case the new child is on top and UNDEFINED. + final int activityType = task != null ? task.getActivityType() : ACTIVITY_TYPE_UNDEFINED; + + super.addChild(child, index); + + if (isAddingActivity && task != null) { + child.asActivityRecord().inHistory = true; + task.onDescendantActivityAdded(taskHadActivity, activityType, child.asActivityRecord()); + } + } + + @Override + void onChildPositionChanged(WindowContainer child) { + super.onChildPositionChanged(child); + + sendTaskFragmentInfoChanged(); + } + + void executeAppTransition(ActivityOptions options) { + // No app transition applied to the task fragment. + } + + @Override + RemoteAnimationTarget createRemoteAnimationTarget( + RemoteAnimationController.RemoteAnimationRecord record) { + final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING + // There may be a trampoline activity without window on top of the existing task + // which is moving to front. Exclude the finishing activity so the window of next + // activity can be chosen to create the animation target. + ? getTopNonFinishingActivity() + : getTopMostActivity(); + return activity != null ? activity.createRemoteAnimationTarget(record) : null; + } + + @Override + boolean canCreateRemoteAnimationTarget() { + return true; + } + + boolean shouldSleepActivities() { + return false; + } + + @Override + void resolveOverrideConfiguration(Configuration newParentConfig) { + mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds()); + super.resolveOverrideConfiguration(newParentConfig); + + int windowingMode = + getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); + final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode(); + + // Resolve override windowing mode to fullscreen for home task (even on freeform + // display), or split-screen if in split-screen mode. + if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) { + windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode) + ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN; + getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode); + } + + // Do not allow tasks not support multi window to be in a multi-window mode, unless it is in + // pinned windowing mode. + if (!supportsMultiWindow()) { + final int candidateWindowingMode = + windowingMode != WINDOWING_MODE_UNDEFINED ? windowingMode : parentWindowingMode; + if (WindowConfiguration.inMultiWindowMode(candidateWindowingMode) + && candidateWindowingMode != WINDOWING_MODE_PINNED) { + getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode( + WINDOWING_MODE_FULLSCREEN); + } + } + + final Task thisTask = asTask(); + // Embedded Task's configuration should go with parent TaskFragment, so we don't re-compute + // configuration here. + if (thisTask != null && !thisTask.isEmbedded()) { + thisTask.resolveLeafTaskOnlyOverrideConfigs(newParentConfig, + mTmpBounds /* previousBounds */); + } + computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig); + } + + boolean supportsMultiWindow() { + return supportsMultiWindowInDisplayArea(getDisplayArea()); + } + + /** + * @return whether this task supports multi-window if it is in the given + * {@link TaskDisplayArea}. + */ + boolean supportsMultiWindowInDisplayArea(@Nullable TaskDisplayArea tda) { + if (!mAtmService.mSupportsMultiWindow) { + return false; + } + final Task task = getTask(); + if (task == null) { + return false; + } + if (tda == null) { + Slog.w(TAG, "Can't find TaskDisplayArea to determine support for multi" + + " window. Task id=" + getTaskId() + " attached=" + isAttached()); + return false; + } + if (!getTask().isResizeable() && !tda.supportsNonResizableMultiWindow()) { + // Not support non-resizable in multi window. + return false; + } + + final ActivityRecord rootActivity = getTask().getRootActivity(); + return tda.supportsActivityMinWidthHeightMultiWindow(mMinWidth, mMinHeight, + rootActivity != null ? rootActivity.info : null); + } + + private int getTaskId() { + return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID; + } + + /** + * Ensures all visible activities at or below the input activity have the right configuration. + */ + void ensureVisibleActivitiesConfiguration(ActivityRecord start, boolean preserveWindow) { + mEnsureVisibleActivitiesConfigHelper.process(start, preserveWindow); + } + + void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, + @NonNull Configuration parentConfig) { + computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */, + null /* compatInsets */); + } + + void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, + @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) { + if (overrideDisplayInfo != null) { + // Make sure the screen related configs can be computed by the provided display info. + inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED; + invalidateAppBoundsConfig(inOutConfig); + } + computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo, + null /* compatInsets */); + } + + void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, + @NonNull Configuration parentConfig, + @Nullable ActivityRecord.CompatDisplayInsets compatInsets) { + if (compatInsets != null) { + // Make sure the app bounds can be computed by the compat insets. + invalidateAppBoundsConfig(inOutConfig); + } + computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */, + compatInsets); + } + + /** + * Forces the app bounds related configuration can be computed by + * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo, + * ActivityRecord.CompatDisplayInsets)}. + */ + private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) { + final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds(); + if (appBounds != null) { + appBounds.setEmpty(); + } + inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED; + inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; + } + + /** + * Calculates configuration values used by the client to get resources. This should be run + * using app-facing bounds (bounds unmodified by animations or transient interactions). + * + * This assumes bounds are non-empty/null. For the null-bounds case, the caller is likely + * configuring an "inherit-bounds" window which means that all configuration settings would + * just be inherited from the parent configuration. + **/ + void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, + @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo, + @Nullable ActivityRecord.CompatDisplayInsets compatInsets) { + int windowingMode = inOutConfig.windowConfiguration.getWindowingMode(); + if (windowingMode == WINDOWING_MODE_UNDEFINED) { + windowingMode = parentConfig.windowConfiguration.getWindowingMode(); + } + + float density = inOutConfig.densityDpi; + if (density == Configuration.DENSITY_DPI_UNDEFINED) { + density = parentConfig.densityDpi; + } + density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; + + // The bounds may have been overridden at this level. If the parent cannot cover these + // bounds, the configuration is still computed according to the override bounds. + final boolean insideParentBounds; + + final Rect parentBounds = parentConfig.windowConfiguration.getBounds(); + final Rect resolvedBounds = inOutConfig.windowConfiguration.getBounds(); + if (resolvedBounds == null || resolvedBounds.isEmpty()) { + mTmpFullBounds.set(parentBounds); + insideParentBounds = true; + } else { + mTmpFullBounds.set(resolvedBounds); + insideParentBounds = parentBounds.contains(resolvedBounds); + } + + // Non-null compatibility insets means the activity prefers to keep its original size, so + // out bounds doesn't need to be restricted by the parent or current display + final boolean customContainerPolicy = compatInsets != null; + + Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + if (outAppBounds == null || outAppBounds.isEmpty()) { + // App-bounds hasn't been overridden, so calculate a value for it. + inOutConfig.windowConfiguration.setAppBounds(mTmpFullBounds); + outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + + if (!customContainerPolicy && windowingMode != WINDOWING_MODE_FREEFORM) { + final Rect containingAppBounds; + if (insideParentBounds) { + containingAppBounds = parentConfig.windowConfiguration.getAppBounds(); + } else { + // Restrict appBounds to display non-decor rather than parent because the + // override bounds are beyond the parent. Otherwise, it won't match the + // overridden bounds. + final TaskDisplayArea displayArea = getDisplayArea(); + containingAppBounds = displayArea != null + ? displayArea.getWindowConfiguration().getAppBounds() : null; + } + if (containingAppBounds != null && !containingAppBounds.isEmpty()) { + outAppBounds.intersect(containingAppBounds); + } + } + } + + if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED + || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { + if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) { + mTmpNonDecorBounds.set(mTmpFullBounds); + mTmpStableBounds.set(mTmpFullBounds); + } else if (!customContainerPolicy + && (overrideDisplayInfo != null || getDisplayContent() != null)) { + final DisplayInfo di = overrideDisplayInfo != null + ? overrideDisplayInfo + : getDisplayContent().getDisplayInfo(); + + // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen + // area, i.e. the screen area without the system bars. + // The non decor inset are areas that could never be removed in Honeycomb. See + // {@link WindowManagerPolicy#getNonDecorInsetsLw}. + calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di); + } else { + // Apply the given non-decor and stable insets to calculate the corresponding bounds + // for screen size of configuration. + int rotation = inOutConfig.windowConfiguration.getRotation(); + if (rotation == ROTATION_UNDEFINED) { + rotation = parentConfig.windowConfiguration.getRotation(); + } + if (rotation != ROTATION_UNDEFINED && customContainerPolicy) { + mTmpNonDecorBounds.set(mTmpFullBounds); + mTmpStableBounds.set(mTmpFullBounds); + compatInsets.getBoundsByRotation(mTmpBounds, rotation); + intersectWithInsetsIfFits(mTmpNonDecorBounds, mTmpBounds, + compatInsets.mNonDecorInsets[rotation]); + intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds, + compatInsets.mStableInsets[rotation]); + outAppBounds.set(mTmpNonDecorBounds); + } else { + // Set to app bounds because it excludes decor insets. + mTmpNonDecorBounds.set(outAppBounds); + mTmpStableBounds.set(outAppBounds); + } + } + + if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { + final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density); + inOutConfig.screenWidthDp = (insideParentBounds && !customContainerPolicy) + ? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp) + : overrideScreenWidthDp; + } + if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { + final int overrideScreenHeightDp = (int) (mTmpStableBounds.height() / density); + inOutConfig.screenHeightDp = (insideParentBounds && !customContainerPolicy) + ? Math.min(overrideScreenHeightDp, parentConfig.screenHeightDp) + : overrideScreenHeightDp; + } + + if (inOutConfig.smallestScreenWidthDp + == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { + // When entering to or exiting from Pip, the PipTaskOrganizer will set the + // windowing mode of the activity in the task to WINDOWING_MODE_FULLSCREEN and + // temporarily set the bounds of the task to fullscreen size for transitioning. + // It will get the wrong value if the calculation is based on this temporary + // fullscreen bounds. + // We should just inherit the value from parent for this temporary state. + final boolean inPipTransition = windowingMode == WINDOWING_MODE_PINNED + && !mTmpFullBounds.isEmpty() && mTmpFullBounds.equals(parentBounds); + if (WindowConfiguration.isFloating(windowingMode) && !inPipTransition) { + // For floating tasks, calculate the smallest width from the bounds of the task + inOutConfig.smallestScreenWidthDp = (int) ( + Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density); + } + // otherwise, it will just inherit + } + } + + if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { + inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) + ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; + } + if (inOutConfig.screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) { + // For calculating screen layout, we need to use the non-decor inset screen area for the + // calculation for compatibility reasons, i.e. screen area without system bars that + // could never go away in Honeycomb. + int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density); + int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density); + // Use overrides if provided. If both overrides are provided, mTmpNonDecorBounds is + // undefined so it can't be used. + if (inOutConfig.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) { + compatScreenWidthDp = inOutConfig.screenWidthDp; + } + if (inOutConfig.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { + compatScreenHeightDp = inOutConfig.screenHeightDp; + } + // Reducing the screen layout starting from its parent config. + inOutConfig.screenLayout = computeScreenLayoutOverride(parentConfig.screenLayout, + compatScreenWidthDp, compatScreenHeightDp); + } + } + + /** + * Gets bounds with non-decor and stable insets applied respectively. + * + * If bounds overhangs the display, those edges will not get insets. See + * {@link #intersectWithInsetsIfFits} + * + * @param outNonDecorBounds where to place bounds with non-decor insets applied. + * @param outStableBounds where to place bounds with stable insets applied. + * @param bounds the bounds to inset. + */ + void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds, + DisplayInfo displayInfo) { + outNonDecorBounds.set(bounds); + outStableBounds.set(bounds); + final Task rootTask = getRootTaskFragment().asTask(); + if (rootTask == null || rootTask.mDisplayContent == null) { + return; + } + mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight); + + final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy(); + policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth, + displayInfo.logicalHeight, displayInfo.displayCutout, mTmpInsets); + intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets); + + policy.convertNonDecorInsetsToStableInsets(mTmpInsets, displayInfo.rotation); + intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets); + } + + /** + * Intersects inOutBounds with intersectBounds-intersectInsets. If inOutBounds is larger than + * intersectBounds on a side, then the respective side will not be intersected. + * + * The assumption is that if inOutBounds is initially larger than intersectBounds, then the + * inset on that side is no-longer applicable. This scenario happens when a task's minimal + * bounds are larger than the provided parent/display bounds. + * + * @param inOutBounds the bounds to intersect. + * @param intersectBounds the bounds to intersect with. + * @param intersectInsets insets to apply to intersectBounds before intersecting. + */ + static void intersectWithInsetsIfFits( + Rect inOutBounds, Rect intersectBounds, Rect intersectInsets) { + if (inOutBounds.right <= intersectBounds.right) { + inOutBounds.right = + Math.min(intersectBounds.right - intersectInsets.right, inOutBounds.right); + } + if (inOutBounds.bottom <= intersectBounds.bottom) { + inOutBounds.bottom = + Math.min(intersectBounds.bottom - intersectInsets.bottom, inOutBounds.bottom); + } + if (inOutBounds.left >= intersectBounds.left) { + inOutBounds.left = + Math.max(intersectBounds.left + intersectInsets.left, inOutBounds.left); + } + if (inOutBounds.top >= intersectBounds.top) { + inOutBounds.top = + Math.max(intersectBounds.top + intersectInsets.top, inOutBounds.top); + } + } + + /** Computes LONG, SIZE and COMPAT parts of {@link Configuration#screenLayout}. */ + static int computeScreenLayoutOverride(int sourceScreenLayout, int screenWidthDp, + int screenHeightDp) { + sourceScreenLayout = sourceScreenLayout + & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK); + final int longSize = Math.max(screenWidthDp, screenHeightDp); + final int shortSize = Math.min(screenWidthDp, screenHeightDp); + return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize); + } + + @Override + public int getActivityType() { + final int applicationType = super.getActivityType(); + if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) { + return applicationType; + } + return getTopChild().getActivityType(); + } + + @Override + public void onConfigurationChanged(Configuration newParentConfig) { + // Task will animate differently. + if (mTaskFragmentOrganizer != null) { + mTmpPrevBounds.set(getBounds()); + } + + super.onConfigurationChanged(newParentConfig); + + if (shouldStartChangeTransition(mTmpPrevBounds)) { + initializeChangeTransition(mTmpPrevBounds); + } else if (mTaskFragmentOrganizer != null) { + // Update the surface here instead of in the organizer so that we can make sure + // it can be synced with the surface freezer. + final SurfaceControl.Transaction t = getSyncTransaction(); + updateSurfacePosition(t); + updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */); + } + + sendTaskFragmentInfoChanged(); + } + + /** Updates the surface size so that the sub windows cannot be shown out of bounds. */ + private void updateOrganizedTaskFragmentSurfaceSize(SurfaceControl.Transaction t, + boolean forceUpdate) { + if (mTaskFragmentOrganizer == null) { + // We only want to update for organized TaskFragment. Task will handle itself. + return; + } + if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) { + return; + } + + final Rect bounds = getBounds(); + final int width = bounds.width(); + final int height = bounds.height(); + if (!forceUpdate && width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) { + return; + } + t.setWindowCrop(mSurfaceControl, width, height); + mLastSurfaceSize.set(width, height); + } + + @Override + public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) { + super.onAnimationLeashCreated(t, leash); + // Reset surface bounds for animation. It will be taken care by the animation leash, and + // reset again onAnimationLeashLost. + if (mTaskFragmentOrganizer != null + && (mLastSurfaceSize.x != 0 || mLastSurfaceSize.y != 0)) { + t.setWindowCrop(mSurfaceControl, 0, 0); + mLastSurfaceSize.set(0, 0); + } + } + + @Override + public void onAnimationLeashLost(SurfaceControl.Transaction t) { + super.onAnimationLeashLost(t); + // Update the surface bounds after animation. + if (mTaskFragmentOrganizer != null) { + updateOrganizedTaskFragmentSurfaceSize(t, true /* forceUpdate */); + } + } + + /** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */ + private boolean shouldStartChangeTransition(Rect startBounds) { + if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) { + return false; + } + + return !startBounds.equals(getBounds()); + } + + @Override + void setSurfaceControl(SurfaceControl sc) { + super.setSurfaceControl(sc); + if (mTaskFragmentOrganizer != null) { + final SurfaceControl.Transaction t = getSyncTransaction(); + updateSurfacePosition(t); + updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */); + // If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to + // emit the callbacks now. + sendTaskFragmentAppeared(); + } + } + + void sendTaskFragmentInfoChanged() { + if (mTaskFragmentOrganizer != null) { + mTaskFragmentOrganizerController + .onTaskFragmentInfoChanged(mTaskFragmentOrganizer, this); + } + } + + private void sendTaskFragmentAppeared() { + if (mTaskFragmentOrganizer != null) { + mTaskFragmentOrganizerController.onTaskFragmentAppeared(mTaskFragmentOrganizer, this); + } + } + + private void sendTaskFragmentVanished() { + if (mTaskFragmentOrganizer != null) { + mTaskFragmentOrganizerController.onTaskFragmentVanished(mTaskFragmentOrganizer, this); + } + } + + /** + * Returns a {@link TaskFragmentInfo} with information from this TaskFragment. Should not be + * called from {@link Task}. + */ + TaskFragmentInfo getTaskFragmentInfo() { + List<IBinder> childActivities = new ArrayList<>(); + for (int i = 0; i < getChildCount(); i++) { + final WindowContainer wc = getChildAt(i); + final ActivityRecord ar = wc.asActivityRecord(); + if (mTaskFragmentOrganizerUid != INVALID_UID && ar != null + && ar.info.processName.equals(mTaskFragmentOrganizerProcessName) + && ar.getUid() == mTaskFragmentOrganizerUid && !ar.finishing) { + // Only includes Activities that belong to the organizer process for security. + childActivities.add(ar.appToken); + } + } + final Point positionInParent = new Point(); + getRelativePosition(positionInParent); + final int[] runningActivityCount = new int[1]; + forAllActivities(a -> { + if (!a.finishing) { + runningActivityCount[0]++; + } + }); + return new TaskFragmentInfo( + mFragmentToken, + mRemoteToken.toWindowContainerToken(), + getConfiguration(), + getChildCount() == 0, + runningActivityCount[0], + isVisible(), + childActivities, + positionInParent, + mClearedTaskForReuse); + } + + @Nullable + IBinder getFragmentToken() { + return mFragmentToken; + } + + @Nullable + ITaskFragmentOrganizer getTaskFragmentOrganizer() { + return mTaskFragmentOrganizer; + } + + @Override + boolean isOrganized() { + return mTaskFragmentOrganizer != null; + } + + /** Whether this is an organized {@link TaskFragment} and not a {@link Task}. */ + final boolean isOrganizedTaskFragment() { + return mTaskFragmentOrganizer != null; + } + + boolean isReadyToTransit() { + // We don't want to start the transition if the organized TaskFragment is empty, unless + // it is requested to be removed. + return !isOrganizedTaskFragment() || getTopNonFinishingActivity() != null + || mIsRemovalRequested; + } + + /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */ + void clearLastPausedActivity() { + forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null); + } + + /** + * Sets {@link #mMinWidth} and {@link #mMinWidth} to this TaskFragment. + * It is usually set from the parent {@link Task} when adding the TaskFragment to the window + * hierarchy. + */ + void setMinDimensions(int minWidth, int minHeight) { + if (asTask() != null) { + throw new UnsupportedOperationException("This method must not be used to Task. The " + + " minimum dimension of Task should be passed from Task constructor."); + } + mMinWidth = minWidth; + mMinHeight = minHeight; + } + + @Override + void removeChild(WindowContainer child) { + removeChild(child, true /* removeSelfIfPossible */); + } + + void removeChild(WindowContainer child, boolean removeSelfIfPossible) { + super.removeChild(child); + if (removeSelfIfPossible && (!mCreatedByOrganizer || mIsRemovalRequested) && !hasChild()) { + removeImmediately("removeLastChild " + child); + } + } + + /** + * Requests to remove this task fragment. If it doesn't have children, it is removed + * immediately. Otherwise it will be removed until all activities are destroyed. + * + * @param withTransition Whether to use transition animation when removing activities. Set to + * {@code false} if this is invisible to user, e.g. display removal. + */ + void remove(boolean withTransition, String reason) { + if (!hasChild()) { + removeImmediately(reason); + return; + } + mIsRemovalRequested = true; + forAllActivities(r -> { + if (withTransition) { + r.finishIfPossible(reason, false /* oomAdj */); + } else { + r.destroyIfPossible(reason); + } + }); + } + + void setDelayLastActivityRemoval(boolean delay) { + if (!mIsEmbedded) { + Slog.w(TAG, "Set delaying last activity removal on a non-embedded TF."); + } + mDelayLastActivityRemoval = delay; + } + + boolean isDelayLastActivityRemoval() { + return mDelayLastActivityRemoval; + } + + boolean shouldDeferRemoval() { + if (!hasChild()) { + return false; + } + return isAnimating(TRANSITION | CHILDREN, WindowState.EXIT_ANIMATING_TYPES) + || inTransition(); + } + + @Override + boolean handleCompleteDeferredRemoval() { + if (shouldDeferRemoval()) { + return true; + } + return super.handleCompleteDeferredRemoval(); + } + + /** The overridden method must call {@link #removeImmediately()} instead of super. */ + void removeImmediately(String reason) { + Slog.d(TAG, "Remove task fragment: " + reason); + removeImmediately(); + } + + @Override + void removeImmediately() { + mIsRemovalRequested = false; + resetAdjacentTaskFragment(); + super.removeImmediately(); + sendTaskFragmentVanished(); + } + + @Override + Dimmer getDimmer() { + // If the window is in an embedded TaskFragment, we want to dim at the TaskFragment. + if (asTask() == null) { + return mDimmer; + } + + return super.getDimmer(); + } + + @Override + void prepareSurfaces() { + if (asTask() != null) { + super.prepareSurfaces(); + return; + } + + mDimmer.resetDimStates(); + super.prepareSurfaces(); + + // Bounds need to be relative, as the dim layer is a child. + final Rect dimBounds = getBounds(); + dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */); + if (mDimmer.updateDims(getPendingTransaction(), dimBounds)) { + scheduleAnimation(); + } + } + + @Override + boolean canBeAnimationTarget() { + return true; + } + + @Override + boolean fillsParent() { + // From the perspective of policy, we still want to report that this task fills parent + // in fullscreen windowing mode even it doesn't match parent bounds because there will be + // letterbox around its real content. + return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds(); + } + + boolean dump(String prefix, FileDescriptor fd, PrintWriter pw, boolean dumpAll, + boolean dumpClient, String dumpPackage, final boolean needSep, Runnable header) { + boolean printed = false; + Runnable headerPrinter = () -> { + if (needSep) { + pw.println(); + } + if (header != null) { + header.run(); + } + + dumpInner(prefix, pw, dumpAll, dumpPackage); + }; + + if (dumpPackage == null) { + // If we are not filtering by package, we want to print absolutely everything, + // so always print the header even if there are no tasks/activities inside. + headerPrinter.run(); + headerPrinter = null; + printed = true; + } + + for (int i = mChildren.size() - 1; i >= 0; --i) { + WindowContainer child = mChildren.get(i); + if (child.asTaskFragment() != null) { + printed |= child.asTaskFragment().dump(prefix + " ", fd, pw, dumpAll, + dumpClient, dumpPackage, needSep, headerPrinter); + } else if (child.asActivityRecord() != null) { + ActivityRecord.dumpActivity(fd, pw, i, child.asActivityRecord(), prefix + " ", + "Hist ", true, !dumpAll, dumpClient, dumpPackage, false, headerPrinter, + getTask()); + } + } + + return printed; + } + + void dumpInner(String prefix, PrintWriter pw, boolean dumpAll, String dumpPackage) { + pw.print(prefix); pw.print("* "); pw.println(this); + final Rect bounds = getRequestedOverrideBounds(); + if (!bounds.isEmpty()) { + pw.println(prefix + " mBounds=" + bounds); + } + if (mIsRemovalRequested) { + pw.println(prefix + " mIsRemovalRequested=true"); + } + if (dumpAll) { + printThisActivity(pw, mLastPausedActivity, dumpPackage, false, + prefix + " mLastPausedActivity: ", null); + } + } + + @Override + void dump(PrintWriter pw, String prefix, boolean dumpAll) { + super.dump(pw, prefix, dumpAll); + pw.println(prefix + "bounds=" + getBounds().toShortString()); + final String doublePrefix = prefix + " "; + for (int i = mChildren.size() - 1; i >= 0; i--) { + final WindowContainer<?> child = mChildren.get(i); + pw.println(prefix + "* " + child); + // Only dump non-activity because full activity info is already printed by + // RootWindowContainer#dumpActivities. + if (child.asActivityRecord() == null) { + child.dump(pw, doublePrefix, dumpAll); + } + } + } + + @Override + void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(HASH_CODE, System.identityHashCode(this)); + final ActivityRecord topActivity = topRunningActivity(); + proto.write(USER_ID, topActivity != null ? topActivity.mUserId : USER_NULL); + proto.write(TITLE, topActivity != null ? topActivity.intent.getComponent() + .flattenToShortString() : "TaskFragment"); + proto.end(token); + } + + @Override + long getProtoFieldId() { + return TASK_FRAGMENT; + } + + @Override + public void dumpDebug(ProtoOutputStream proto, long fieldId, + @WindowTraceLogLevel int logLevel) { + if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) { + return; + } + + final long token = proto.start(fieldId); + + super.dumpDebug(proto, WINDOW_CONTAINER, logLevel); + + proto.write(DISPLAY_ID, getDisplayId()); + proto.write(ACTIVITY_TYPE, getActivityType()); + proto.write(MIN_WIDTH, mMinWidth); + proto.write(MIN_HEIGHT, mMinHeight); + + proto.end(token); + } +} diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java new file mode 100644 index 000000000000..123ca889c73e --- /dev/null +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -0,0 +1,624 @@ +/* + * Copyright (C) 2021 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.window.TaskFragmentOrganizer.putExceptionInBundle; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; +import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Slog; +import android.view.RemoteAnimationDefinition; +import android.window.ITaskFragmentOrganizer; +import android.window.ITaskFragmentOrganizerController; +import android.window.TaskFragmentInfo; + +import com.android.internal.protolog.common.ProtoLog; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Stores and manages the client {@link android.window.TaskFragmentOrganizer}. + */ +public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerController.Stub { + private static final String TAG = "TaskFragmentOrganizerController"; + + private final ActivityTaskManagerService mAtmService; + private final WindowManagerGlobalLock mGlobalLock; + /** + * A Map which manages the relationship between + * {@link ITaskFragmentOrganizer} and {@link TaskFragmentOrganizerState} + */ + private final ArrayMap<IBinder, TaskFragmentOrganizerState> mTaskFragmentOrganizerState = + new ArrayMap<>(); + /** + * A List which manages the TaskFragment pending event {@link PendingTaskFragmentEvent} + */ + private final ArrayList<PendingTaskFragmentEvent> mPendingTaskFragmentEvents = + new ArrayList<>(); + + TaskFragmentOrganizerController(ActivityTaskManagerService atm) { + mAtmService = atm; + mGlobalLock = atm.mGlobalLock; + } + + /** + * A class to manage {@link ITaskFragmentOrganizer} and its organized + * {@link TaskFragment TaskFragments}. + */ + private class TaskFragmentOrganizerState implements IBinder.DeathRecipient { + private final ArrayList<TaskFragment> mOrganizedTaskFragments = new ArrayList<>(); + private final ITaskFragmentOrganizer mOrganizer; + private final Map<TaskFragment, TaskFragmentInfo> mLastSentTaskFragmentInfos = + new WeakHashMap<>(); + private final Map<TaskFragment, Configuration> mLastSentTaskFragmentParentConfigs = + new WeakHashMap<>(); + + /** + * @see android.window.TaskFragmentOrganizer#registerRemoteAnimations( + * RemoteAnimationDefinition) + */ + @Nullable + private RemoteAnimationDefinition mRemoteAnimationDefinition; + + TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer) { + mOrganizer = organizer; + try { + mOrganizer.asBinder().linkToDeath(this, 0 /*flags*/); + } catch (RemoteException e) { + Slog.e(TAG, "TaskFragmentOrganizer failed to register death recipient"); + } + } + + @Override + public void binderDied() { + synchronized (mGlobalLock) { + removeOrganizer(mOrganizer); + } + } + + /** + * @return {@code true} if taskFragment is organized and not sent the appeared event before. + */ + boolean addTaskFragment(TaskFragment taskFragment) { + if (taskFragment.mTaskFragmentAppearedSent) { + return false; + } + if (mOrganizedTaskFragments.contains(taskFragment)) { + return false; + } + mOrganizedTaskFragments.add(taskFragment); + return true; + } + + void removeTaskFragment(TaskFragment taskFragment) { + mOrganizedTaskFragments.remove(taskFragment); + } + + void dispose() { + while (!mOrganizedTaskFragments.isEmpty()) { + final TaskFragment taskFragment = mOrganizedTaskFragments.get(0); + taskFragment.removeImmediately(); + mOrganizedTaskFragments.remove(taskFragment); + } + mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/); + } + + void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment tf) { + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName()); + final TaskFragmentInfo info = tf.getTaskFragmentInfo(); + try { + organizer.onTaskFragmentAppeared(info); + mLastSentTaskFragmentInfos.put(tf, info); + tf.mTaskFragmentAppearedSent = true; + } catch (RemoteException e) { + Slog.e(TAG, "Exception sending onTaskFragmentAppeared callback", e); + } + onTaskFragmentParentInfoChanged(organizer, tf); + } + + void onTaskFragmentVanished(ITaskFragmentOrganizer organizer, TaskFragment tf) { + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment vanished name=%s", tf.getName()); + try { + organizer.onTaskFragmentVanished(tf.getTaskFragmentInfo()); + } catch (RemoteException e) { + Slog.e(TAG, "Exception sending onTaskFragmentVanished callback", e); + } + tf.mTaskFragmentAppearedSent = false; + mLastSentTaskFragmentInfos.remove(tf); + mLastSentTaskFragmentParentConfigs.remove(tf); + } + + void onTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment tf) { + // Parent config may have changed. The controller will check if there is any important + // config change for the organizer. + onTaskFragmentParentInfoChanged(organizer, tf); + + // Check if the info is different from the last reported info. + final TaskFragmentInfo info = tf.getTaskFragmentInfo(); + final TaskFragmentInfo lastInfo = mLastSentTaskFragmentInfos.get(tf); + if (info.equalsForTaskFragmentOrganizer(lastInfo) && configurationsAreEqualForOrganizer( + info.getConfiguration(), lastInfo.getConfiguration())) { + return; + } + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment info changed name=%s", + tf.getName()); + try { + organizer.onTaskFragmentInfoChanged(tf.getTaskFragmentInfo()); + mLastSentTaskFragmentInfos.put(tf, info); + } catch (RemoteException e) { + Slog.e(TAG, "Exception sending onTaskFragmentInfoChanged callback", e); + } + } + + void onTaskFragmentParentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment tf) { + // Check if the parent info is different from the last reported parent info. + if (tf.getParent() == null || tf.getParent().asTask() == null) { + mLastSentTaskFragmentParentConfigs.remove(tf); + return; + } + final Task parent = tf.getParent().asTask(); + final Configuration parentConfig = parent.getConfiguration(); + final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(tf); + if (configurationsAreEqualForOrganizer(parentConfig, lastParentConfig)) { + return; + } + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, + "TaskFragment parent info changed name=%s parentTaskId=%d", + tf.getName(), parent.mTaskId); + try { + organizer.onTaskFragmentParentInfoChanged(tf.getFragmentToken(), parentConfig); + mLastSentTaskFragmentParentConfigs.put(tf, new Configuration(parentConfig)); + } catch (RemoteException e) { + Slog.e(TAG, "Exception sending onTaskFragmentParentInfoChanged callback", e); + } + } + + void onTaskFragmentError(ITaskFragmentOrganizer organizer, IBinder errorCallbackToken, + Throwable exception) { + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, + "Sending TaskFragment error exception=%s", exception.toString()); + final Bundle exceptionBundle = putExceptionInBundle(exception); + try { + organizer.onTaskFragmentError(errorCallbackToken, exceptionBundle); + } catch (RemoteException e) { + Slog.e(TAG, "Exception sending onTaskFragmentError callback", e); + } + } + } + + @Override + public void registerOrganizer(ITaskFragmentOrganizer organizer) { + final int pid = Binder.getCallingPid(); + final long uid = Binder.getCallingUid(); + synchronized (mGlobalLock) { + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, + "Register task fragment organizer=%s uid=%d pid=%d", + organizer.asBinder(), uid, pid); + if (mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) { + throw new IllegalStateException( + "Replacing existing organizer currently unsupported"); + } + mTaskFragmentOrganizerState.put(organizer.asBinder(), + new TaskFragmentOrganizerState(organizer)); + } + } + + @Override + public void unregisterOrganizer(ITaskFragmentOrganizer organizer) { + validateAndGetState(organizer); + final int pid = Binder.getCallingPid(); + final long uid = Binder.getCallingUid(); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, + "Unregister task fragment organizer=%s uid=%d pid=%d", + organizer.asBinder(), uid, pid); + removeOrganizer(organizer); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + @Override + public void registerRemoteAnimations(ITaskFragmentOrganizer organizer, + RemoteAnimationDefinition definition) { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + synchronized (mGlobalLock) { + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, + "Register remote animations for organizer=%s uid=%d pid=%d", + organizer.asBinder(), uid, pid); + final TaskFragmentOrganizerState organizerState = + mTaskFragmentOrganizerState.get(organizer.asBinder()); + if (organizerState == null) { + throw new IllegalStateException("The organizer hasn't been registered."); + } + if (organizerState.mRemoteAnimationDefinition != null) { + throw new IllegalStateException( + "The organizer has already registered remote animations=" + + organizerState.mRemoteAnimationDefinition); + } + + definition.setCallingPidUid(pid, uid); + organizerState.mRemoteAnimationDefinition = definition; + } + } + + @Override + public void unregisterRemoteAnimations(ITaskFragmentOrganizer organizer) { + final int pid = Binder.getCallingPid(); + final long uid = Binder.getCallingUid(); + synchronized (mGlobalLock) { + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, + "Unregister remote animations for organizer=%s uid=%d pid=%d", + organizer.asBinder(), uid, pid); + final TaskFragmentOrganizerState organizerState = + mTaskFragmentOrganizerState.get(organizer.asBinder()); + if (organizerState == null) { + Slog.e(TAG, "The organizer hasn't been registered."); + return; + } + + organizerState.mRemoteAnimationDefinition = null; + } + } + + /** Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. */ + @Nullable + public RemoteAnimationDefinition getRemoteAnimationDefinition( + ITaskFragmentOrganizer organizer) { + synchronized (mGlobalLock) { + final TaskFragmentOrganizerState organizerState = + mTaskFragmentOrganizerState.get(organizer.asBinder()); + return organizerState != null ? organizerState.mRemoteAnimationDefinition : null; + } + } + + void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) { + final TaskFragmentOrganizerState state = validateAndGetState(organizer); + if (!state.addTaskFragment(taskFragment)) { + return; + } + PendingTaskFragmentEvent pendingEvent = getPendingTaskFragmentEvent(taskFragment, + PendingTaskFragmentEvent.EVENT_APPEARED); + if (pendingEvent == null) { + pendingEvent = new PendingTaskFragmentEvent(taskFragment, organizer, + PendingTaskFragmentEvent.EVENT_APPEARED); + mPendingTaskFragmentEvents.add(pendingEvent); + } + } + + void onTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) { + handleTaskFragmentInfoChanged(organizer, taskFragment, + PendingTaskFragmentEvent.EVENT_INFO_CHANGED); + } + + void onTaskFragmentParentInfoChanged(ITaskFragmentOrganizer organizer, + TaskFragment taskFragment) { + handleTaskFragmentInfoChanged(organizer, taskFragment, + PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED); + } + + private void handleTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer, + TaskFragment taskFragment, int eventType) { + validateAndGetState(organizer); + if (!taskFragment.mTaskFragmentAppearedSent) { + // Skip if TaskFragment still not appeared. + return; + } + PendingTaskFragmentEvent pendingEvent = getLastPendingLifecycleEvent(taskFragment); + if (pendingEvent == null) { + pendingEvent = new PendingTaskFragmentEvent(taskFragment, organizer, eventType); + } else { + if (pendingEvent.mEventType == PendingTaskFragmentEvent.EVENT_VANISHED) { + // Skipped the info changed event if vanished event is pending. + return; + } + // Remove and add for re-ordering. + mPendingTaskFragmentEvents.remove(pendingEvent); + } + mPendingTaskFragmentEvents.add(pendingEvent); + } + + void onTaskFragmentVanished(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) { + final TaskFragmentOrganizerState state = validateAndGetState(organizer); + for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) { + PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i); + if (taskFragment == entry.mTaskFragment) { + mPendingTaskFragmentEvents.remove(i); + if (entry.mEventType == PendingTaskFragmentEvent.EVENT_APPEARED) { + // If taskFragment appeared callback is pending, ignore the vanished request. + return; + } + } + } + if (!taskFragment.mTaskFragmentAppearedSent) { + return; + } + PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent(taskFragment, + organizer, PendingTaskFragmentEvent.EVENT_VANISHED); + mPendingTaskFragmentEvents.add(pendingEvent); + state.removeTaskFragment(taskFragment); + } + + void onTaskFragmentError(ITaskFragmentOrganizer organizer, IBinder errorCallbackToken, + Throwable exception) { + validateAndGetState(organizer); + Slog.w(TAG, "onTaskFragmentError ", exception); + PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent(organizer, + errorCallbackToken, exception, PendingTaskFragmentEvent.EVENT_ERROR); + mPendingTaskFragmentEvents.add(pendingEvent); + } + + private void removeOrganizer(ITaskFragmentOrganizer organizer) { + final TaskFragmentOrganizerState state = validateAndGetState(organizer); + // remove all of the children of the organized TaskFragment + state.dispose(); + mTaskFragmentOrganizerState.remove(organizer.asBinder()); + } + + /** + * Makes sure that the organizer has been correctly registered to prevent any Sidecar + * implementation from organizing {@link TaskFragment} without registering first. In such case, + * we wouldn't register {@link DeathRecipient} for the organizer, and might not remove the + * {@link TaskFragment} after the organizer process died. + */ + private TaskFragmentOrganizerState validateAndGetState(ITaskFragmentOrganizer organizer) { + final TaskFragmentOrganizerState state = + mTaskFragmentOrganizerState.get(organizer.asBinder()); + if (state == null) { + throw new IllegalArgumentException( + "TaskFragmentOrganizer has not been registered. Organizer=" + organizer); + } + return state; + } + + /** + * A class to store {@link ITaskFragmentOrganizer} and its organized + * {@link TaskFragment TaskFragments} with different pending event request. + */ + private static class PendingTaskFragmentEvent { + static final int EVENT_APPEARED = 0; + static final int EVENT_VANISHED = 1; + static final int EVENT_INFO_CHANGED = 2; + static final int EVENT_PARENT_INFO_CHANGED = 3; + static final int EVENT_ERROR = 4; + + @IntDef(prefix = "EVENT_", value = { + EVENT_APPEARED, + EVENT_VANISHED, + EVENT_INFO_CHANGED, + EVENT_PARENT_INFO_CHANGED, + EVENT_ERROR + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EventType {} + + @EventType + private final int mEventType; + private final ITaskFragmentOrganizer mTaskFragmentOrg; + private final TaskFragment mTaskFragment; + private final IBinder mErrorCallback; + private final Throwable mException; + // Set when the event is deferred due to the host task is invisible. The defer time will + // be the last active time of the host task. + private long mDeferTime; + + private PendingTaskFragmentEvent(TaskFragment taskFragment, + ITaskFragmentOrganizer taskFragmentOrg, @EventType int eventType) { + this(taskFragment, taskFragmentOrg, null /* errorCallback */, + null /* exception */, eventType); + + } + + private PendingTaskFragmentEvent(ITaskFragmentOrganizer taskFragmentOrg, + IBinder errorCallback, Throwable exception, @EventType int eventType) { + this(null /* taskFragment */, taskFragmentOrg, errorCallback, exception, + eventType); + } + + private PendingTaskFragmentEvent(TaskFragment taskFragment, + ITaskFragmentOrganizer taskFragmentOrg, IBinder errorCallback, Throwable exception, + @EventType int eventType) { + mTaskFragment = taskFragment; + mTaskFragmentOrg = taskFragmentOrg; + mErrorCallback = errorCallback; + mException = exception; + mEventType = eventType; + } + + /** + * @return {@code true} if the pending event is related with taskFragment created, vanished + * and information changed. + */ + boolean isLifecycleEvent() { + switch (mEventType) { + case EVENT_APPEARED: + case EVENT_VANISHED: + case EVENT_INFO_CHANGED: + case EVENT_PARENT_INFO_CHANGED: + return true; + default: + return false; + } + } + } + + @Nullable + private PendingTaskFragmentEvent getLastPendingLifecycleEvent(TaskFragment tf) { + for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) { + PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i); + if (tf == entry.mTaskFragment && entry.isLifecycleEvent()) { + return entry; + } + } + return null; + } + + @Nullable + private PendingTaskFragmentEvent getPendingTaskFragmentEvent(TaskFragment taskFragment, + int type) { + for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) { + PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i); + if (taskFragment == entry.mTaskFragment && type == entry.mEventType) { + return entry; + } + } + return null; + } + + private boolean shouldSendEventWhenTaskInvisible(@NonNull Task task, + @NonNull PendingTaskFragmentEvent event) { + final TaskFragmentOrganizerState state = + mTaskFragmentOrganizerState.get(event.mTaskFragmentOrg.asBinder()); + final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos.get(event.mTaskFragment); + final TaskFragmentInfo info = event.mTaskFragment.getTaskFragmentInfo(); + // Send an info changed callback if this event is for the last activities to finish in a + // Task so that the {@link TaskFragmentOrganizer} can delete this TaskFragment. Otherwise, + // the Task may be removed before it becomes visible again to send this event because it no + // longer has activities. As a result, the organizer will never get this info changed event + // and will not delete the TaskFragment because the organizer thinks the TaskFragment still + // has running activities. + return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED + && task.topRunningActivity() == null && lastInfo != null + && lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0; + } + + void dispatchPendingEvents() { + if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred() + || mPendingTaskFragmentEvents.isEmpty()) { + return; + } + + final ArrayList<Task> visibleTasks = new ArrayList<>(); + final ArrayList<Task> invisibleTasks = new ArrayList<>(); + final ArrayList<PendingTaskFragmentEvent> candidateEvents = new ArrayList<>(); + for (int i = 0, n = mPendingTaskFragmentEvents.size(); i < n; i++) { + final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i); + final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null; + if (task != null && (task.lastActiveTime <= event.mDeferTime + || !(isTaskVisible(task, visibleTasks, invisibleTasks) + || shouldSendEventWhenTaskInvisible(task, event)))) { + // Defer sending events to the TaskFragment until the host task is active again. + event.mDeferTime = task.lastActiveTime; + continue; + } + candidateEvents.add(event); + } + final int numEvents = candidateEvents.size(); + for (int i = 0; i < numEvents; i++) { + dispatchEvent(candidateEvents.get(i)); + } + if (numEvents > 0) { + mPendingTaskFragmentEvents.removeAll(candidateEvents); + } + } + + private static boolean isTaskVisible(Task task, ArrayList<Task> knownVisibleTasks, + ArrayList<Task> knownInvisibleTasks) { + if (knownVisibleTasks.contains(task)) { + return true; + } + if (knownInvisibleTasks.contains(task)) { + return false; + } + if (task.shouldBeVisible(null /* starting */)) { + knownVisibleTasks.add(task); + return true; + } else { + knownInvisibleTasks.add(task); + return false; + } + } + + void dispatchPendingInfoChangedEvent(TaskFragment taskFragment) { + PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment, + PendingTaskFragmentEvent.EVENT_INFO_CHANGED); + if (event == null) { + return; + } + + dispatchEvent(event); + mPendingTaskFragmentEvents.remove(event); + } + + private void dispatchEvent(PendingTaskFragmentEvent event) { + final ITaskFragmentOrganizer taskFragmentOrg = event.mTaskFragmentOrg; + final TaskFragment taskFragment = event.mTaskFragment; + final TaskFragmentOrganizerState state = + mTaskFragmentOrganizerState.get(taskFragmentOrg.asBinder()); + if (state == null) { + return; + } + switch (event.mEventType) { + case PendingTaskFragmentEvent.EVENT_APPEARED: + state.onTaskFragmentAppeared(taskFragmentOrg, taskFragment); + break; + case PendingTaskFragmentEvent.EVENT_VANISHED: + state.onTaskFragmentVanished(taskFragmentOrg, taskFragment); + break; + case PendingTaskFragmentEvent.EVENT_INFO_CHANGED: + state.onTaskFragmentInfoChanged(taskFragmentOrg, taskFragment); + break; + case PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED: + state.onTaskFragmentParentInfoChanged(taskFragmentOrg, taskFragment); + break; + case PendingTaskFragmentEvent.EVENT_ERROR: + state.onTaskFragmentError(taskFragmentOrg, event.mErrorCallback, + event.mException); + } + } + + // TODO(b/204399167): change to push the embedded state to the client side + @Override + public boolean isActivityEmbedded(IBinder activityToken) { + synchronized (mGlobalLock) { + final ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken); + if (activity == null) { + return false; + } + final TaskFragment taskFragment = activity.getOrganizedTaskFragment(); + if (taskFragment == null) { + return false; + } + final Task parentTask = taskFragment.getTask(); + if (parentTask != null) { + final Rect taskBounds = parentTask.getBounds(); + final Rect taskFragBounds = taskFragment.getBounds(); + return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds); + } + return false; + } + } +} diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index f43cd7a80ede..b8ceb4a4f421 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -261,7 +261,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { if (launchMode == WINDOWING_MODE_PINNED) { if (DEBUG) appendLog("picture-in-picture"); } else if (!root.isResizeable()) { - if (shouldLaunchUnresizableAppInFreeform(root, suggestedDisplayArea)) { + if (shouldLaunchUnresizableAppInFreeform(root, suggestedDisplayArea, options)) { launchMode = WINDOWING_MODE_FREEFORM; if (outParams.mBounds.isEmpty()) { getTaskBounds(root, suggestedDisplayArea, layout, launchMode, @@ -617,7 +617,11 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } private boolean shouldLaunchUnresizableAppInFreeform(ActivityRecord activity, - TaskDisplayArea displayArea) { + TaskDisplayArea displayArea, @Nullable ActivityOptions options) { + if (options != null && options.getLaunchWindowingMode() == WINDOWING_MODE_FULLSCREEN) { + // Do not launch the activity in freeform if it explicitly requested fullscreen mode. + return false; + } if (!activity.supportsFreeformInDisplayArea(displayArea) || activity.isResizeable()) { return false; } @@ -713,7 +717,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } // First we get the default size we want. - getDefaultFreeformSize(displayArea, layout, orientation, mTmpBounds); + getDefaultFreeformSize(root.info, displayArea, layout, orientation, mTmpBounds); if (hasInitialBounds || sizeMatches(inOutBounds, mTmpBounds)) { // We're here because either input parameters specified initial bounds, or the suggested // bounds have the same size of the default freeform size. We should use the suggested @@ -781,7 +785,8 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { return orientation; } - private void getDefaultFreeformSize(@NonNull TaskDisplayArea displayArea, + private void getDefaultFreeformSize(@NonNull ActivityInfo info, + @NonNull TaskDisplayArea displayArea, @NonNull ActivityInfo.WindowLayout layout, int orientation, @NonNull Rect bounds) { // Default size, which is letterboxing/pillarboxing in displayArea. That's to say the large // dimension of default size is the small dimension of displayArea size, and the small @@ -812,11 +817,38 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { final int layoutMinWidth = (layout == null) ? -1 : layout.minWidth; final int layoutMinHeight = (layout == null) ? -1 : layout.minHeight; - // Final result. + // Aspect ratio requirements. + final float minAspectRatio = info.getMinAspectRatio(orientation); + final float maxAspectRatio = info.getMaxAspectRatio(); + final int width = Math.min(defaultWidth, Math.max(phoneWidth, layoutMinWidth)); final int height = Math.min(defaultHeight, Math.max(phoneHeight, layoutMinHeight)); + final float aspectRatio = (float) Math.max(width, height) / (float) Math.min(width, height); + + // Adjust the width and height to the aspect ratio requirements. + int adjWidth = width; + int adjHeight = height; + if (minAspectRatio >= 1 && aspectRatio < minAspectRatio) { + // The aspect ratio is below the minimum, adjust it to the minimum. + if (orientation == SCREEN_ORIENTATION_LANDSCAPE) { + // Fix the width, scale the height. + adjHeight = (int) (adjWidth / minAspectRatio + 0.5f); + } else { + // Fix the height, scale the width. + adjWidth = (int) (adjHeight / minAspectRatio + 0.5f); + } + } else if (maxAspectRatio >= 1 && aspectRatio > maxAspectRatio) { + // The aspect ratio exceeds the maximum, adjust it to the maximum. + if (orientation == SCREEN_ORIENTATION_LANDSCAPE) { + // Fix the width, scale the height. + adjHeight = (int) (adjWidth / maxAspectRatio + 0.5f); + } else { + // Fix the height, scale the width. + adjWidth = (int) (adjHeight / maxAspectRatio + 0.5f); + } + } - bounds.set(0, 0, width, height); + bounds.set(0, 0, adjWidth, adjHeight); bounds.offset(stableBounds.left, stableBounds.top); } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 88467baa6c34..3d5f9881e044 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -16,24 +16,19 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; - import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL; -import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_CONFIGS; -import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_WINDOW_CONFIGS; +import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.ParceledListSlice; -import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; @@ -45,6 +40,7 @@ import android.window.ITaskOrganizer; import android.window.ITaskOrganizerController; import android.window.SplashScreenView; import android.window.StartingWindowInfo; +import android.window.StartingWindowRemovalInfo; import android.window.TaskAppearedInfo; import android.window.TaskSnapshot; import android.window.WindowContainerToken; @@ -69,21 +65,6 @@ import java.util.function.Consumer; class TaskOrganizerController extends ITaskOrganizerController.Stub { private static final String TAG = "TaskOrganizerController"; - /** - * Masks specifying which configurations are important to report back to an organizer when - * changed. - */ - private static final int REPORT_CONFIGS = CONTROLLABLE_CONFIGS; - private static final int REPORT_WINDOW_CONFIGS = CONTROLLABLE_WINDOW_CONFIGS; - - // The set of modes that are currently supports - // TODO: Remove once the task organizer can support all modes - @VisibleForTesting - static final int[] UNSUPPORTED_WINDOWING_MODES = { - WINDOWING_MODE_UNDEFINED, - WINDOWING_MODE_FREEFORM - }; - private class DeathRecipient implements IBinder.DeathRecipient { ITaskOrganizer mTaskOrganizer; @@ -122,109 +103,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return mTaskOrganizer.asBinder(); } - void addStartingWindow(Task task, ActivityRecord activity, int launchTheme, - TaskSnapshot taskSnapshot) { - final StartingWindowInfo info = task.getStartingWindowInfo(activity); - if (launchTheme != 0) { - info.splashScreenThemeResId = launchTheme; - } - info.mTaskSnapshot = taskSnapshot; - // make this happen prior than prepare surface - try { - mTaskOrganizer.addStartingWindow(info, activity.token); - } catch (RemoteException e) { - Slog.e(TAG, "Exception sending onTaskStart callback", e); - } - } - - // Capture the animation surface control for activity's main window - private class StartingWindowAnimationAdaptor implements AnimationAdapter { - private SurfaceControl mAnimationLeash; - @Override - public boolean getShowWallpaper() { - return false; - } - - @Override - public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, - int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { - mAnimationLeash = animationLeash; - } - - @Override - public void onAnimationCancelled(SurfaceControl animationLeash) { - if (mAnimationLeash == animationLeash) { - mAnimationLeash = null; - } - } - - @Override - public long getDurationHint() { - return 0; - } - - @Override - public long getStatusBarTransitionsStartTime() { - return 0; - } - - @Override - public void dump(PrintWriter pw, String prefix) { - pw.print(prefix + "StartingWindowAnimationAdaptor mCapturedLeash="); - pw.print(mAnimationLeash); - pw.println(); - } - - @Override - public void dumpDebug(ProtoOutputStream proto) { - } - } - - void removeStartingWindow(Task task, boolean prepareAnimation) { - SurfaceControl windowAnimationLeash = null; - Rect mainFrame = null; - final boolean playShiftUpAnimation = !task.inMultiWindowMode(); - if (prepareAnimation && playShiftUpAnimation) { - final ActivityRecord topActivity = task.topActivityContainsStartingWindow(); - if (topActivity != null) { - final WindowState mainWindow = - topActivity.findMainWindow(false/* includeStartingApp */); - if (mainWindow != null) { - final StartingWindowAnimationAdaptor adaptor = - new StartingWindowAnimationAdaptor(); - final SurfaceControl.Transaction t = mainWindow.getPendingTransaction(); - mainWindow.startAnimation(t, adaptor, false, - ANIMATION_TYPE_STARTING_REVEAL); - windowAnimationLeash = adaptor.mAnimationLeash; - mainFrame = mainWindow.getRelativeFrame(); - t.setPosition(windowAnimationLeash, mainFrame.left, mainFrame.top); - } - } - } - try { - mTaskOrganizer.removeStartingWindow(task.mTaskId, windowAnimationLeash, - mainFrame, prepareAnimation); - } catch (RemoteException e) { - Slog.e(TAG, "Exception sending onStartTaskFinished callback", e); - } - } - - void copySplashScreenView(Task task) { - try { - mTaskOrganizer.copySplashScreenView(task.mTaskId); - } catch (RemoteException e) { - Slog.e(TAG, "Exception sending copyStartingWindowView callback", e); - } - } - - void onAppSplashScreenViewRemoved(Task task) { - try { - mTaskOrganizer.onAppSplashScreenViewRemoved(task.mTaskId); - } catch (RemoteException e) { - Slog.e(TAG, "Exception sending onAppSplashScreenViewRemoved callback", e); - } - } - SurfaceControl prepareLeash(Task task, String reason) { return new SurfaceControl(task.getSurfaceControl(), reason); } @@ -311,23 +189,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mUid = uid; } - void addStartingWindow(Task t, ActivityRecord activity, int launchTheme, - TaskSnapshot taskSnapshot) { - mOrganizer.addStartingWindow(t, activity, launchTheme, taskSnapshot); - } - - void removeStartingWindow(Task t, boolean prepareAnimation) { - mOrganizer.removeStartingWindow(t, prepareAnimation); - } - - void copySplashScreenView(Task t) { - mOrganizer.copySplashScreenView(t); - } - - public void onAppSplashScreenViewRemoved(Task t) { - mOrganizer.onAppSplashScreenViewRemoved(t); - } - /** * Register this task with this state, but doesn't trigger the task appeared callback to * the organizer. @@ -389,6 +250,15 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mOrganizer.mTaskOrganizer, t); } } + if (mService.getTransitionController().isShellTransitionsEnabled()) { + // dispose is only called outside of transitions (eg during unregister). Since + // we "migrate" surfaces when replacing organizers, visibility gets delegated + // to transitions; however, since there is no transition at this point, we have + // to manually show the surface here. + if (t.mTaskOrganizer != null && t.getSurfaceControl() != null) { + t.getSyncTransaction().show(t.getSurfaceControl()); + } + } } // Remove organizer state after removing tasks so we get a chance to send @@ -481,7 +351,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { final int uid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); try { - synchronized (mGlobalLock) { + final ArrayList<TaskAppearedInfo> taskInfos = new ArrayList<>(); + final Runnable withGlobalLock = () -> { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Register task organizer=%s uid=%d", organizer.asBinder(), uid); if (!mTaskOrganizerStates.containsKey(organizer.asBinder())) { @@ -490,24 +361,27 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { new TaskOrganizerState(organizer, uid)); } - final ArrayList<TaskAppearedInfo> taskInfos = new ArrayList<>(); final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); mService.mRootWindowContainer.forAllTasks((task) -> { - if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, task.getWindowingMode())) { - return; - } - boolean returnTask = !task.mCreatedByOrganizer; task.updateTaskOrganizerState(true /* forceUpdate */, returnTask /* skipTaskAppeared */); if (returnTask) { SurfaceControl outSurfaceControl = state.addTaskWithoutCallback(task, "TaskOrganizerController.registerTaskOrganizer"); - taskInfos.add(new TaskAppearedInfo(task.getTaskInfo(), outSurfaceControl)); + taskInfos.add( + new TaskAppearedInfo(task.getTaskInfo(), outSurfaceControl)); } }); - return new ParceledListSlice<>(taskInfos); + }; + if (mService.getTransitionController().isShellTransitionsEnabled()) { + mService.getTransitionController().mRunningLock.runWhenIdle(1000, withGlobalLock); + } else { + synchronized (mGlobalLock) { + withGlobalLock.run(); + } } + return new ParceledListSlice<>(taskInfos); } finally { Binder.restoreCallingIdentity(origId); } @@ -519,7 +393,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { final int uid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); try { - synchronized (mGlobalLock) { + final Runnable withGlobalLock = () -> { final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); if (state == null) { return; @@ -528,6 +402,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { organizer.asBinder(), uid); state.unlinkDeath(); state.dispose(); + }; + if (mService.getTransitionController().isShellTransitionsEnabled()) { + mService.getTransitionController().mRunningLock.runWhenIdle(1000, withGlobalLock); + } else { + synchronized (mGlobalLock) { + withGlobalLock.run(); + } } } finally { Binder.restoreCallingIdentity(origId); @@ -537,46 +418,136 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { /** * @return the task organizer key for a given windowing mode. */ - ITaskOrganizer getTaskOrganizer(int windowingMode) { - return isSupportedWindowingMode(windowingMode) - ? mTaskOrganizers.peekLast() - : null; + ITaskOrganizer getTaskOrganizer() { + return mTaskOrganizers.peekLast(); } - boolean isSupportedWindowingMode(int winMode) { - return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode); + // Capture the animation surface control for activity's main window + static class StartingWindowAnimationAdaptor implements AnimationAdapter { + SurfaceControl mAnimationLeash; + @Override + public boolean getShowWallpaper() { + return false; + } + + @Override + public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, + int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + mAnimationLeash = animationLeash; + } + + @Override + public void onAnimationCancelled(SurfaceControl animationLeash) { + if (mAnimationLeash == animationLeash) { + mAnimationLeash = null; + } + } + + @Override + public long getDurationHint() { + return 0; + } + + @Override + public long getStatusBarTransitionsStartTime() { + return 0; + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix + "StartingWindowAnimationAdaptor mCapturedLeash="); + pw.print(mAnimationLeash); + pw.println(); + } + + @Override + public void dumpDebug(ProtoOutputStream proto) { + } + } + + static SurfaceControl applyStartingWindowAnimation(WindowContainer window) { + final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor(); + window.startAnimation(window.getPendingTransaction(), adaptor, false, + ANIMATION_TYPE_STARTING_REVEAL); + return adaptor.mAnimationLeash; } boolean addStartingWindow(Task task, ActivityRecord activity, int launchTheme, TaskSnapshot taskSnapshot) { final Task rootTask = task.getRootTask(); - if (rootTask == null || rootTask.mTaskOrganizer == null || activity.mStartingData == null) { + if (rootTask == null || activity.mStartingData == null) { + return false; + } + final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast(); + if (lastOrganizer == null) { + return false; + } + final StartingWindowInfo info = task.getStartingWindowInfo(activity); + if (launchTheme != 0) { + info.splashScreenThemeResId = launchTheme; + } + info.taskSnapshot = taskSnapshot; + // make this happen prior than prepare surface + try { + lastOrganizer.addStartingWindow(info, activity.token); + } catch (RemoteException e) { + Slog.e(TAG, "Exception sending onTaskStart callback", e); return false; } - final TaskOrganizerState state = - mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder()); - state.addStartingWindow(task, activity, launchTheme, taskSnapshot); return true; } void removeStartingWindow(Task task, boolean prepareAnimation) { final Task rootTask = task.getRootTask(); - if (rootTask == null || rootTask.mTaskOrganizer == null) { + if (rootTask == null) { + return; + } + final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast(); + if (lastOrganizer == null) { return; } - final TaskOrganizerState state = - mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder()); - state.removeStartingWindow(task, prepareAnimation); + final StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo(); + removalInfo.taskId = task.mTaskId; + removalInfo.playRevealAnimation = prepareAnimation; + final boolean playShiftUpAnimation = !task.inMultiWindowMode(); + final ActivityRecord topActivity = task.topActivityContainsStartingWindow(); + if (topActivity != null) { + removalInfo.deferRemoveForIme = topActivity.mDisplayContent + .mayImeShowOnLaunchingActivity(topActivity); + if (prepareAnimation && playShiftUpAnimation) { + final WindowState mainWindow = + topActivity.findMainWindow(false/* includeStartingApp */); + if (mainWindow != null) { + final SurfaceControl.Transaction t = mainWindow.getPendingTransaction(); + removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow); + removalInfo.mainFrame = mainWindow.getRelativeFrame(); + t.setPosition(removalInfo.windowAnimationLeash, + removalInfo.mainFrame.left, removalInfo.mainFrame.top); + } + } + } + try { + lastOrganizer.removeStartingWindow(removalInfo); + } catch (RemoteException e) { + Slog.e(TAG, "Exception sending onStartTaskFinished callback", e); + } } boolean copySplashScreenView(Task task) { final Task rootTask = task.getRootTask(); - if (rootTask == null || rootTask.mTaskOrganizer == null) { + if (rootTask == null) { + return false; + } + final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast(); + if (lastOrganizer == null) { + return false; + } + try { + lastOrganizer.copySplashScreenView(task.mTaskId); + } catch (RemoteException e) { + Slog.e(TAG, "Exception sending copyStartingWindowView callback", e); return false; } - final TaskOrganizerState state = - mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder()); - state.copySplashScreenView(task); return true; } @@ -588,12 +559,18 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { */ public void onAppSplashScreenViewRemoved(Task task) { final Task rootTask = task.getRootTask(); - if (rootTask == null || rootTask.mTaskOrganizer == null) { + if (rootTask == null) { return; } - final TaskOrganizerState state = - mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder()); - state.onAppSplashScreenViewRemoved(task); + final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast(); + if (lastOrganizer == null) { + return; + } + try { + lastOrganizer.onAppSplashScreenViewRemoved(task.mTaskId); + } catch (RemoteException e) { + Slog.e(TAG, "Exception sending onAppSplashScreenViewRemoved callback", e); + } } void onTaskAppeared(ITaskOrganizer organizer, Task task) { @@ -688,7 +665,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Delete root task display=%d winMode=%d", task.getDisplayId(), task.getWindowingMode()); - task.removeImmediately("deleteRootTask"); + task.remove(true /* withTransition */, "deleteRootTask"); return true; } } finally { @@ -718,6 +695,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { if (state != null) { state.mOrganizer.onTaskVanished(task); } + mLastSentTaskInfos.remove(task); break; case PendingTaskEvent.EVENT_INFO_CHANGED: dispatchTaskInfoChanged(event.mTask, event.mForce); @@ -777,18 +755,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mTmpTaskInfo.configuration.unset(); task.fillTaskInfo(mTmpTaskInfo); - boolean changed = !mTmpTaskInfo.equalsForTaskOrganizer(lastInfo); - if (!changed) { - int cfgChanges = mTmpTaskInfo.configuration.diff(lastInfo.configuration); - final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0 - ? (int) mTmpTaskInfo.configuration.windowConfiguration.diff( - lastInfo.configuration.windowConfiguration, - true /* compareUndefined */) : 0; - if ((winCfgChanges & REPORT_WINDOW_CONFIGS) == 0) { - cfgChanges &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION; - } - changed = (cfgChanges & REPORT_CONFIGS) != 0; - } + boolean changed = !mTmpTaskInfo.equalsForTaskOrganizer(lastInfo) + || !configurationsAreEqualForOrganizer( + mTmpTaskInfo.configuration, lastInfo.configuration); if (!(changed || force)) { // mTmpTaskInfo will be reused next time. return; @@ -1021,9 +990,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { for (int k = 0; k < tasks.size(); k++) { final Task task = tasks.get(k); final int mode = task.getWindowingMode(); - if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, mode)) { - continue; - } pw.println(innerPrefix + " (" + WindowConfiguration.windowingModeToString(mode) + ") " + task); } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index e74371036619..141d889c1804 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -54,6 +54,7 @@ import com.android.server.wm.utils.InsetUtils; import com.google.android.collect.Sets; import java.io.PrintWriter; +import java.util.Set; /** * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and @@ -167,7 +168,10 @@ class TaskSnapshotController { * calling {@link #snapshotTasks} to ensure that the task has an up-to-date snapshot. */ @VisibleForTesting - void addSkipClosingAppSnapshotTasks(ArraySet<Task> tasks) { + void addSkipClosingAppSnapshotTasks(Set<Task> tasks) { + if (shouldDisableSnapshots()) { + return; + } mSkipClosingAppSnapshotTasks.addAll(tasks); } @@ -175,46 +179,49 @@ class TaskSnapshotController { snapshotTasks(tasks, false /* allowSnapshotHome */); } - private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) { - for (int i = tasks.size() - 1; i >= 0; i--) { - final Task task = tasks.valueAt(i); - final TaskSnapshot snapshot; - final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome(); - if (snapshotHome) { - snapshot = snapshotTask(task); - } else { - switch (getSnapshotMode(task)) { - case SNAPSHOT_MODE_NONE: - continue; - case SNAPSHOT_MODE_APP_THEME: - snapshot = drawAppThemeSnapshot(task); - break; - case SNAPSHOT_MODE_REAL: - snapshot = snapshotTask(task); - break; - default: - snapshot = null; - break; - } + void recordTaskSnapshot(Task task, boolean allowSnapshotHome) { + final TaskSnapshot snapshot; + final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome(); + if (snapshotHome) { + snapshot = snapshotTask(task); + } else { + switch (getSnapshotMode(task)) { + case SNAPSHOT_MODE_NONE: + return; + case SNAPSHOT_MODE_APP_THEME: + snapshot = drawAppThemeSnapshot(task); + break; + case SNAPSHOT_MODE_REAL: + snapshot = snapshotTask(task); + break; + default: + snapshot = null; + break; } - if (snapshot != null) { - final HardwareBuffer buffer = snapshot.getHardwareBuffer(); - if (buffer.getWidth() == 0 || buffer.getHeight() == 0) { - buffer.close(); - Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x" - + buffer.getHeight()); - } else { - mCache.putSnapshot(task, snapshot); - // Don't persist or notify the change for the temporal snapshot. - if (!snapshotHome) { - mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); - task.onSnapshotChanged(snapshot); - } + } + if (snapshot != null) { + final HardwareBuffer buffer = snapshot.getHardwareBuffer(); + if (buffer.getWidth() == 0 || buffer.getHeight() == 0) { + buffer.close(); + Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x" + + buffer.getHeight()); + } else { + mCache.putSnapshot(task, snapshot); + // Don't persist or notify the change for the temporal snapshot. + if (!snapshotHome) { + mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); + task.onSnapshotChanged(snapshot); } } } } + private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) { + for (int i = tasks.size() - 1; i >= 0; i--) { + recordTaskSnapshot(tasks.valueAt(i), allowSnapshotHome); + } + } + /** * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW * MANAGER LOCK WHEN CALLING THIS METHOD! @@ -283,11 +290,13 @@ class TaskSnapshotController { final WindowState mainWindow = result.second; final Rect contentInsets = getSystemBarInsets(task.getBounds(), mainWindow.getInsetsStateWithVisibilityOverride()); - InsetUtils.addInsets(contentInsets, activity.getLetterboxInsets()); + final Rect letterboxInsets = activity.getLetterboxInsets(); + InsetUtils.addInsets(contentInsets, letterboxInsets); builder.setIsRealSnapshot(true); builder.setId(System.currentTimeMillis()); builder.setContentInsets(contentInsets); + builder.setLetterboxInsets(letterboxInsets); final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE; final boolean isShowWallpaper = mainWindow.hasWallpaper(); @@ -568,7 +577,8 @@ class TaskSnapshotController { return null; } final Rect contentInsets = new Rect(systemBarInsets); - InsetUtils.addInsets(contentInsets, topChild.getLetterboxInsets()); + final Rect letterboxInsets = topChild.getLetterboxInsets(); + InsetUtils.addInsets(contentInsets, letterboxInsets); // Note, the app theme snapshot is never translucent because we enforce a non-translucent // color above @@ -577,9 +587,9 @@ class TaskSnapshotController { topChild.mActivityComponent, hwBitmap.getHardwareBuffer(), hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation, mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight), - contentInsets, false /* isLowResolution */, false /* isRealSnapshot */, - task.getWindowingMode(), getAppearance(task), false /* isTranslucent */, - false /* hasImeSurface */); + contentInsets, letterboxInsets, false /* isLowResolution */, + false /* isRealSnapshot */, task.getWindowingMode(), + getAppearance(task), false /* isTranslucent */, false /* hasImeSurface */); } /** @@ -683,7 +693,8 @@ class TaskSnapshotController { } static Rect getSystemBarInsets(Rect frame, InsetsState state) { - return state.calculateInsets(frame, Type.systemBars(), false /* ignoreVisibility */); + return state.calculateInsets( + frame, Type.systemBars(), false /* ignoreVisibility */).toRect(); } void dump(PrintWriter pw, String prefix) { diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java index d3bfbab8106f..9189e51f860c 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java @@ -20,7 +20,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.app.ActivityManager; -import android.window.TaskSnapshot; import android.content.ComponentName; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; @@ -30,6 +29,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.util.Slog; +import android.window.TaskSnapshot; import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto; @@ -196,6 +196,8 @@ class TaskSnapshotLoader { return new TaskSnapshot(proto.id, topActivityComponent, buffer, hwBitmap.getColorSpace(), proto.orientation, proto.rotation, taskSize, new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom), + new Rect(proto.letterboxInsetLeft, proto.letterboxInsetTop, + proto.letterboxInsetRight, proto.letterboxInsetBottom), loadLowResolutionBitmap, proto.isRealSnapshot, proto.windowingMode, proto.appearance, proto.isTranslucent, false /* hasImeSurface */); } catch (IOException e) { diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java index 60c4766ea18f..9fbcd7cc5d84 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java @@ -23,7 +23,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.annotation.TestApi; -import android.window.TaskSnapshot; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.os.Process; @@ -31,6 +30,7 @@ import android.os.SystemClock; import android.util.ArraySet; import android.util.AtomicFile; import android.util.Slog; +import android.window.TaskSnapshot; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -380,6 +380,10 @@ class TaskSnapshotPersister { proto.insetTop = mSnapshot.getContentInsets().top; proto.insetRight = mSnapshot.getContentInsets().right; proto.insetBottom = mSnapshot.getContentInsets().bottom; + proto.letterboxInsetLeft = mSnapshot.getLetterboxInsets().left; + proto.letterboxInsetTop = mSnapshot.getLetterboxInsets().top; + proto.letterboxInsetRight = mSnapshot.getLetterboxInsets().right; + proto.letterboxInsetBottom = mSnapshot.getLetterboxInsets().bottom; proto.isRealSnapshot = mSnapshot.isRealSnapshot(); proto.windowingMode = mSnapshot.getWindowingMode(); proto.appearance = mSnapshot.getAppearance(); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index cc4abab01b72..059eb876ad94 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -72,6 +72,7 @@ import android.util.Slog; import android.view.IWindowSession; import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.View; @@ -166,6 +167,7 @@ class TaskSnapshotSurface implements StartingSurface { final ClientWindowFrames tmpFrames = new ClientWindowFrames(); final Rect taskBounds; final InsetsState mTmpInsetsState = new InsetsState(); + final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0]; final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration(); final TaskDescription taskDescription = new TaskDescription(); @@ -227,7 +229,8 @@ class TaskSnapshotSurface implements StartingSurface { int displayId = activity.getDisplayContent().getDisplayId(); try { final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId, - mTmpInsetsState, null /* outInputChannel */, mTmpInsetsState, mTempControls); + mRequestedVisibilities, null /* outInputChannel */, mTmpInsetsState, + mTempControls); if (res < 0) { Slog.w(TAG, "Failed to add snapshot starting window res=" + res); return null; diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 0cd098070401..4db8ef49a11a 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -16,11 +16,19 @@ package com.android.server.wm; - +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.INVALID_DISPLAY; +import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; +import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; +import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; @@ -31,34 +39,45 @@ import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.view.WindowManager.TransitionFlags; +import static android.view.WindowManager.TransitionType; import static android.view.WindowManager.transitTypeToString; +import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; +import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; +import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; +import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN; +import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN; + import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; +import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.SystemClock; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; import android.view.SurfaceControl; -import android.view.WindowManager; import android.view.animation.Animation; -import android.window.IRemoteTransition; +import android.window.RemoteTransition; import android.window.TransitionInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.util.function.pooled.PooledLambda; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -100,12 +119,12 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe @Retention(RetentionPolicy.SOURCE) @interface TransitionState {} - final @WindowManager.TransitionType int mType; + final @TransitionType int mType; private int mSyncId; - private @WindowManager.TransitionFlags int mFlags; + private @TransitionFlags int mFlags; private final TransitionController mController; private final BLASTSyncEngine mSyncEngine; - private IRemoteTransition mRemoteTransition = null; + private RemoteTransition mRemoteTransition = null; /** Only use for clean-up after binder death! */ private SurfaceControl.Transaction mStartTransaction = null; @@ -124,16 +143,53 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe /** The final animation targets derived from participants after promotion. */ private ArraySet<WindowContainer> mTargets = null; + /** + * Set of participating windowtokens (activity/wallpaper) which are visible at the end of + * the transition animation. + */ + private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>(); + + /** Set of transient activities (lifecycle initially tied to this transition). */ + private ArraySet<ActivityRecord> mTransientLaunches = null; + + /** Custom activity-level animation options and callbacks. */ + private TransitionInfo.AnimationOptions mOverrideOptions; + private IRemoteCallback mClientAnimationStartCallback = null; + private IRemoteCallback mClientAnimationFinishCallback = null; + private @TransitionState int mState = STATE_COLLECTING; - private boolean mReadyCalled = false; + private final ReadyTracker mReadyTracker = new ReadyTracker(); + + // TODO(b/188595497): remove when not needed. + /** @see RecentsAnimationController#mNavigationBarAttachedToApp */ + private boolean mNavBarAttachedToApp = false; + private int mRecentsDisplayId = INVALID_DISPLAY; - Transition(@WindowManager.TransitionType int type, @WindowManager.TransitionFlags int flags, + Transition(@TransitionType int type, @TransitionFlags int flags, long timeoutMs, TransitionController controller, BLASTSyncEngine syncEngine) { mType = type; mFlags = flags; mController = controller; mSyncEngine = syncEngine; - mSyncId = mSyncEngine.startSyncSet(this); + mSyncId = mSyncEngine.startSyncSet(this, timeoutMs); + } + + void addFlag(int flag) { + mFlags |= flag; + } + + /** Records an activity as transient-launch. This activity must be already collected. */ + void setTransientLaunch(@NonNull ActivityRecord activity) { + if (mTransientLaunches == null) { + mTransientLaunches = new ArraySet<>(); + } + mTransientLaunches.add(activity); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as " + + "transient-launch", mSyncId, activity); + } + + boolean isTransientLaunch(@NonNull ActivityRecord activity) { + return mTransientLaunches != null && mTransientLaunches.contains(activity); } @VisibleForTesting @@ -141,6 +197,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return mSyncId; } + @TransitionFlags + int getFlags() { + return mFlags; + } + /** * Formally starts the transition. Participants can be collected before this is started, * but this won't consider itself ready until started -- even if all the participants have @@ -153,9 +214,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mState = STATE_STARTED; ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d", mSyncId); - if (mReadyCalled) { - setReady(); - } + applyReady(); } /** @@ -170,6 +229,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe for (WindowContainer curr = wc.getParent(); curr != null && !mChanges.containsKey(curr); curr = curr.getParent()) { mChanges.put(curr, new ChangeInfo(curr)); + if (isReadyGroup(curr)) { + mReadyTracker.addGroup(curr); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for" + + " Transition %d with root=%s", mSyncId, curr); + } } if (mParticipants.contains(wc)) return; mSyncEngine.addToSyncSet(mSyncId, wc); @@ -207,25 +271,76 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } /** + * Specifies configuration change explicitly for the window container, so it can be chosen as + * transition target. This is usually used with transition mode + * {@link android.view.WindowManager#TRANSIT_CHANGE}. + */ + void setKnownConfigChanges(WindowContainer<?> wc, @ActivityInfo.Config int changes) { + final ChangeInfo changeInfo = mChanges.get(wc); + if (changeInfo != null) { + changeInfo.mKnownConfigChanges = changes; + } + } + + private void sendRemoteCallback(@Nullable IRemoteCallback callback) { + if (callback == null) return; + mController.mAtm.mH.sendMessage(PooledLambda.obtainMessage(cb -> { + try { + cb.sendResult(null); + } catch (RemoteException e) { } + }, callback)); + } + + /** + * Set animation options for collecting transition by ActivityRecord. + * @param options AnimationOptions captured from ActivityOptions + */ + void setOverrideAnimation(TransitionInfo.AnimationOptions options, + @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) { + if (mSyncId < 0) return; + mOverrideOptions = options; + sendRemoteCallback(mClientAnimationStartCallback); + mClientAnimationStartCallback = startCallback; + mClientAnimationFinishCallback = finishCallback; + } + + /** * Call this when all known changes related to this transition have been applied. Until * all participants have finished drawing, the transition can still collect participants. * * If this is called before the transition is started, it will be deferred until start. + * + * @param wc A reference point to determine which ready-group to update. For now, each display + * has its own ready-group, so this is used to look-up which display to mark ready. + * The transition will wait for all groups to be ready. */ - void setReady(boolean ready) { + void setReady(WindowContainer wc, boolean ready) { if (mSyncId < 0) return; - if (mState < STATE_STARTED) { - mReadyCalled = ready; - return; - } + mReadyTracker.setReadyFrom(wc, ready); + applyReady(); + } + + private void applyReady() { + if (mState < STATE_STARTED) return; + final boolean ready = mReadyTracker.allReady(); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Set transition ready=%b %d", ready, mSyncId); mSyncEngine.setReady(mSyncId, ready); } - /** @see #setReady . This calls with parameter true. */ - void setReady() { - setReady(true); + /** + * Sets all possible ready groups to ready. + * @see ReadyTracker#setAllReady. + */ + void setAllReady() { + if (mSyncId < 0) return; + mReadyTracker.setAllReady(); + applyReady(); + } + + @VisibleForTesting + boolean allReady() { + return mReadyTracker.allReady(); } /** @@ -275,20 +390,82 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } // Commit all going-invisible containers + boolean activitiesWentInvisible = false; for (int i = 0; i < mParticipants.size(); ++i) { final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); - if (ar != null && !ar.isVisibleRequested()) { - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - " Commit activity becoming invisible: %s", ar); - ar.commitVisibility(false /* visible */, false /* performLayout */); + if (ar != null) { + boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar); + // We need both the expected visibility AND current requested-visibility to be + // false. If it is expected-visible but not currently visible, it means that + // another animation is queued-up to animate this to invisibility, so we can't + // remove the surfaces yet. If it is currently visible, but not expected-visible, + // then doing commitVisibility here would actually be out-of-order and leave the + // activity in a bad state. + if (!visibleAtTransitionEnd && !ar.isVisibleRequested()) { + boolean commitVisibility = true; + if (ar.getDeferHidingClient() && ar.getTask() != null) { + if (ar.pictureInPictureArgs != null + && ar.pictureInPictureArgs.isAutoEnterEnabled()) { + mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs); + // Avoid commit visibility to false here, or else we will get a sudden + // "flash" / surface going invisible for a split second. + commitVisibility = false; + } else { + mController.mAtm.mTaskSupervisor.mUserLeaving = true; + ar.getTaskFragment().startPausing(false /* uiSleeping */, + null /* resuming */, "finishTransition"); + mController.mAtm.mTaskSupervisor.mUserLeaving = false; + } + } + if (commitVisibility) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " Commit activity becoming invisible: %s", ar); + final Task task = ar.getTask(); + if (task != null && !task.isVisibleRequested() + && mTransientLaunches != null) { + // If transition is transient, then snapshots are taken at end of + // transition. + mController.mTaskSnapshotController.recordTaskSnapshot( + task, false /* allowSnapshotHome */); + } + ar.commitVisibility(false /* visible */, false /* performLayout */, + true /* fromTransition */); + activitiesWentInvisible = true; + } + } + if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) { + // Legacy dispatch relies on this (for now). + ar.mEnteringAnimation = visibleAtTransitionEnd; + } + mController.dispatchLegacyAppTransitionFinished(ar); } final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken(); - if (wt != null && !wt.isVisibleRequested()) { - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - " Commit wallpaper becoming invisible: %s", ar); - wt.commitVisibility(false /* visible */); + if (wt != null) { + final boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(wt); + if (!visibleAtTransitionEnd && !wt.isVisibleRequested()) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " Commit wallpaper becoming invisible: %s", wt); + wt.commitVisibility(false /* visible */); + } } } + if (activitiesWentInvisible) { + // Always schedule stop processing when transition finishes because activities don't + // stop while they are in a transition thus their stop could still be pending. + mController.mAtm.mTaskSupervisor + .scheduleProcessStoppingAndFinishingActivitiesIfNeeded(); + } + + sendRemoteCallback(mClientAnimationFinishCallback); + + legacyRestoreNavigationBarFromApp(); + + if (mRecentsDisplayId != INVALID_DISPLAY) { + // Clean up input monitors (for recents) + final DisplayContent dc = + mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId); + dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */); + } } void abort() { @@ -297,16 +474,18 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (mState != STATE_COLLECTING) { throw new IllegalStateException("Too late to abort."); } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId); + mController.dispatchLegacyAppTransitionCancelled(); mState = STATE_ABORT; // Syncengine abort will call through to onTransactionReady() mSyncEngine.abort(mSyncId); } - void setRemoteTransition(IRemoteTransition remoteTransition) { + void setRemoteTransition(RemoteTransition remoteTransition) { mRemoteTransition = remoteTransition; } - IRemoteTransition getRemoteTransition() { + RemoteTransition getRemoteTransition() { return mRemoteTransition; } @@ -327,6 +506,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mController.mAtm.mRootWindowContainer.getDisplayContent(displayId) .getPendingTransaction().merge(transaction); mSyncId = -1; + mOverrideOptions = null; return; } @@ -340,9 +520,18 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Resolve the animating targets from the participants mTargets = calculateTargets(mParticipants, mChanges); final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges); + info.setAnimationOptions(mOverrideOptions); + + // TODO(b/188669821): Move to animation impl in shell. + handleLegacyRecentsStartBehavior(displayId, info); handleNonAppWindowsInTransition(displayId, mType, mFlags); + reportStartReasonsToLogger(); + + // The callback is only populated for custom activity-level client animations + sendRemoteCallback(mClientAnimationStartCallback); + // Manually show any activities that are visibleRequested. This is needed to properly // support simultaneous animation queueing/merging. Specifically, if transition A makes // an activity invisible, it's finishTransaction (which is applied *after* the animation) @@ -354,12 +543,54 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); if (ar == null || !ar.mVisibleRequested) continue; transaction.show(ar.getSurfaceControl()); + + // Also manually show any non-reported parents. This is necessary in a few cases + // where a task is NOT organized but had its visibility changed within its direct + // parent. An example of this is if an alternate home leaf-task HB is started atop the + // normal home leaf-task HA: these are both in the Home root-task HR, so there will be a + // transition containing HA and HB where HA surface is hidden. If a standard task SA is + // launched on top, then HB finishes, no transition will happen since neither home is + // visible. When SA finishes, the transition contains HR rather than HA. Since home + // leaf-tasks are NOT organized, HA won't be in the transition and thus its surface + // wouldn't be shown. Just show is safe here since all other properties will have + // already been reset by the original hiding-transition's finishTransaction (we can't + // show in the finishTransaction because by then the activity doesn't hide until + // surface placement). + for (WindowContainer p = ar.getParent(); p != null && !mTargets.contains(p); + p = p.getParent()) { + if (p.getSurfaceControl() != null) { + transaction.show(p.getSurfaceControl()); + } + } + } + + // Record windowtokens (activity/wallpaper) that are expected to be visible after the + // transition animation. This will be used in finishTransition to prevent prematurely + // committing visibility. + for (int i = mParticipants.size() - 1; i >= 0; --i) { + final WindowContainer wc = mParticipants.valueAt(i); + if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue; + mVisibleAtTransitionEndTokens.add(wc.asWindowToken()); + } + + // Take task snapshots before the animation so that we can capture IME before it gets + // transferred. If transition is transient, IME won't be moved during the transition and + // the tasks are still live, so we take the snapshot at the end of the transition instead. + if (mTransientLaunches == null) { + for (int i = mParticipants.size() - 1; i >= 0; --i) { + final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); + if (ar == null || ar.isVisibleRequested() || ar.getTask() == null + || ar.getTask().isVisibleRequested()) continue; + mController.mTaskSnapshotController.recordTaskSnapshot( + ar.getTask(), false /* allowSnapshotHome */); + } } mStartTransaction = transaction; mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get(); buildFinishTransaction(mFinishTransaction, info.getRootLeash()); if (mController.getTransitionPlayer() != null) { + mController.dispatchLegacyAppTransitionStarting(info); try { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Calling onTransitionReady: %s", info); @@ -375,6 +606,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe cleanUpOnFailure(); } mSyncId = -1; + mOverrideOptions = null; } /** @@ -391,17 +623,152 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (mFinishTransaction != null) { mFinishTransaction.apply(); } - finishTransition(); + mController.finishTransition(this); + } + + /** @see RecentsAnimationController#attachNavigationBarToApp */ + private void handleLegacyRecentsStartBehavior(int displayId, TransitionInfo info) { + if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) { + return; + } + final DisplayContent dc = + mController.mAtm.mRootWindowContainer.getDisplayContent(displayId); + if (dc == null) return; + mRecentsDisplayId = displayId; + + // Recents has an input-consumer to grab input from the "live tile" app. Set that up here + final InputConsumerImpl recentsAnimationInputConsumer = + dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); + if (recentsAnimationInputConsumer != null) { + // find the top-most going-away activity and the recents activity. The top-most + // is used as layer reference while the recents is used for registering the consumer + // override. + ActivityRecord recentsActivity = null; + ActivityRecord topActivity = null; + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() == null) continue; + final Task task = Task.fromWindowContainerToken( + info.getChanges().get(i).getTaskInfo().token); + if (task == null) continue; + final int activityType = change.getTaskInfo().topActivityType; + final boolean isRecents = activityType == ACTIVITY_TYPE_HOME + || activityType == ACTIVITY_TYPE_RECENTS; + if (isRecents && recentsActivity == null) { + recentsActivity = task.getTopVisibleActivity(); + } else if (!isRecents && topActivity == null) { + topActivity = task.getTopNonFinishingActivity(); + } + } + if (recentsActivity != null && topActivity != null) { + recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set( + topActivity.getBounds()); + dc.getInputMonitor().setActiveRecents(recentsActivity, topActivity); + } + } + + // The rest of this function handles nav-bar reparenting + + if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition() + // Skip the case where the nav bar is controlled by fade rotation. + || dc.getFadeRotationAnimationController() != null) { + return; + } + + WindowContainer topWC = null; + // Find the top-most non-home, closing app. + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change c = info.getChanges().get(i); + if (c.getTaskInfo() == null || c.getTaskInfo().displayId != displayId + || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD + || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) { + continue; + } + topWC = WindowContainer.fromBinder(c.getContainer().asBinder()); + break; + } + if (topWC == null || topWC.inMultiWindowMode()) { + return; + } + + final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar(); + if (navWindow == null || navWindow.mToken == null) { + return; + } + mNavBarAttachedToApp = true; + navWindow.mToken.cancelAnimation(); + final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction(); + final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl(); + t.reparent(navSurfaceControl, topWC.getSurfaceControl()); + t.show(navSurfaceControl); + + final WindowContainer imeContainer = dc.getImeContainer(); + if (imeContainer.isVisible()) { + t.setRelativeLayer(navSurfaceControl, imeContainer.getSurfaceControl(), 1); + } else { + // Place the nav bar on top of anything else in the top activity. + t.setLayer(navSurfaceControl, Integer.MAX_VALUE); + } + if (mController.mStatusBar != null) { + mController.mStatusBar.setNavigationBarLumaSamplingEnabled(displayId, false); + } + } + + /** @see RecentsAnimationController#restoreNavigationBarFromApp */ + void legacyRestoreNavigationBarFromApp() { + if (!mNavBarAttachedToApp) return; + mNavBarAttachedToApp = false; + + if (mRecentsDisplayId == INVALID_DISPLAY) { + Slog.e(TAG, "Reparented navigation bar without a valid display"); + mRecentsDisplayId = DEFAULT_DISPLAY; + } + + if (mController.mStatusBar != null) { + mController.mStatusBar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, true); + } + + final DisplayContent dc = + mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId); + final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar(); + if (navWindow == null) return; + navWindow.setSurfaceTranslationY(0); + + final WindowToken navToken = navWindow.mToken; + if (navToken == null) return; + final SurfaceControl.Transaction t = dc.getPendingTransaction(); + final WindowContainer parent = navToken.getParent(); + t.setLayer(navToken.getSurfaceControl(), navToken.getLastLayer()); + + boolean animate = false; + // Search for the home task. If it is supposed to be visible, then the navbar is not at + // the bottom of the screen, so we need to animate it. + for (int i = 0; i < mTargets.size(); ++i) { + final Task task = mTargets.valueAt(i).asTask(); + if (task == null || !task.isHomeOrRecentsRootTask()) continue; + animate = task.isVisibleRequested(); + break; + } + + if (animate) { + final NavBarFadeAnimationController controller = + new NavBarFadeAnimationController(dc); + controller.fadeWindowToken(true); + } else { + // Reparent the SurfaceControl of nav bar token back. + t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl()); + } } private void handleNonAppWindowsInTransition(int displayId, - @WindowManager.TransitionType int transit, int flags) { + @TransitionType int transit, @TransitionFlags int flags) { final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent(displayId); if (dc == null) { return; } - if (transit == TRANSIT_KEYGUARD_GOING_AWAY + if ((transit == TRANSIT_KEYGUARD_GOING_AWAY + || (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) { if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0 && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0 @@ -419,12 +786,35 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0, (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0, (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0); - mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation( - SystemClock.uptimeMillis(), 0 /* duration */); + if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) { + // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI + // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't + // need to call IKeyguardService#keyguardGoingAway here. + mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation( + SystemClock.uptimeMillis(), 0 /* duration */); + } } if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { - mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange(); + mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange( + true /* keyguardOccludingStarted */); + } + } + + private void reportStartReasonsToLogger() { + // Record transition start in metrics logger. We just assume everything is "DRAWN" + // at this point since splash-screen is a presentation (shell) detail. + ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>(); + for (int i = mParticipants.size() - 1; i >= 0; --i) { + ActivityRecord r = mParticipants.valueAt(i).asActivityRecord(); + if (r == null || !r.mVisibleRequested) continue; + // At this point, r is "ready", but if it's not "ALL ready" then it is probably only + // ready due to starting-window. + reasons.put(r, (r.mStartingData instanceof SplashScreenStartingData + && !r.mLastAllReadyAtSync) + ? APP_TRANSITION_SPLASH_SCREEN : APP_TRANSITION_WINDOWS_DRAWN); } + mController.mAtm.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting( + reasons); } @Override @@ -465,6 +855,22 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return wc.asWallpaperToken() != null; } + private static boolean occludesKeyguard(WindowContainer wc) { + final ActivityRecord ar = wc.asActivityRecord(); + if (ar != null) { + return ar.canShowWhenLocked(); + } + final Task t = wc.asTask(); + if (t != null) { + // Get the top activity which was visible (since this is going away, it will remain + // client visible until the transition is finished). + // skip hidden (or about to hide) apps + final ActivityRecord top = t.getActivity(WindowToken::isClientVisible); + return top != null && top.canShowWhenLocked(); + } + return false; + } + /** * Under some conditions (eg. all visible targets within a parent container are transitioning * the same way) the transition can be "promoted" to the parent container. This means an @@ -612,7 +1018,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // of participants that should always be reported even if they aren't top. for (WindowContainer wc : participants) { // Don't include detached windows. - if (!wc.isAttached()) continue; + if (!wc.isAttached()) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " Rejecting as detached: %s", wc); + continue; + } final ChangeInfo changeInfo = changes.get(wc); @@ -629,15 +1039,19 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (reportIfNotTop(wc)) { tmpList.add(wc); } + // Wallpaper must be the top (regardless of how nested it is in DisplayAreas). + boolean skipIntermediateReports = isWallpaper(wc); for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) { - if (!p.isAttached() || !changes.get(p).hasChanged(p)) { + if (!p.isAttached() || changes.get(p) == null || !changes.get(p).hasChanged(p)) { // Again, we're skipping no-ops break; } if (participants.contains(p)) { topParent = p; break; - } else if (reportIfNotTop(p)) { + } else if (isWallpaper(p)) { + skipIntermediateReports = true; + } else if (reportIfNotTop(p) && !skipIntermediateReports) { tmpList.add(p); } } @@ -707,12 +1121,21 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } /** + * A ready group is defined by a root window-container where all transitioning windows under + * it are expected to animate together as a group. At the moment, this treats each display as + * a ready-group to match the existing legacy transition behavior. + */ + private static boolean isReadyGroup(WindowContainer wc) { + return wc instanceof DisplayContent; + } + + /** * Construct a TransitionInfo object from a set of targets and changes. Also populates the * root surface. */ @VisibleForTesting @NonNull - static TransitionInfo calculateTransitionInfo(int type, int flags, + static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags, ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) { final TransitionInfo out = new TransitionInfo(type, flags); @@ -723,17 +1146,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } // Find the top-most shared ancestor of app targets - WindowContainer ancestor = null; - for (int i = appTargets.size() - 1; i >= 0; --i) { - final WindowContainer wc = appTargets.valueAt(i); - ancestor = wc; - break; - } - if (ancestor == null) { + if (appTargets.isEmpty()) { out.setRootLeash(new SurfaceControl(), 0, 0); return out; } - ancestor = ancestor.getParent(); + WindowContainer ancestor = appTargets.valueAt(appTargets.size() - 1).getParent(); // Go up ancestor parent chain until all targets are descendants. ancestorLoop: @@ -798,6 +1215,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo(); task.fillTaskInfo(tinfo); change.setTaskInfo(tinfo); + change.setRotationAnimation(getTaskRotationAnimation(task)); + final ActivityRecord topMostActivity = task.getTopMostActivity(); + change.setAllowEnterPip(topMostActivity != null + && topMostActivity.checkEnterPictureInPictureAppOpsState()); } out.addChange(change); } @@ -805,6 +1226,27 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return out; } + private static int getTaskRotationAnimation(@NonNull Task task) { + final ActivityRecord top = task.getTopVisibleActivity(); + if (top == null) return ROTATION_ANIMATION_UNSPECIFIED; + final WindowState mainWin = top.findMainWindow(false); + if (mainWin == null) return ROTATION_ANIMATION_UNSPECIFIED; + int anim = mainWin.getRotationAnimationHint(); + if (anim >= 0) return anim; + anim = mainWin.getAttrs().rotationAnimation; + if (anim != ROTATION_ANIMATION_SEAMLESS) return anim; + if (mainWin != task.mDisplayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow() + || !top.matchParentBounds()) { + // At the moment, we only support seamless rotation if there is only one window showing. + return ROTATION_ANIMATION_UNSPECIFIED; + } + return mainWin.getAttrs().rotationAnimation; + } + + boolean getLegacyIsReady() { + return mState == STATE_STARTED && mSyncId >= 0 && mSyncEngine.isReady(mSyncId); + } + static Transition fromBinder(IBinder binder) { return (Transition) binder; } @@ -823,6 +1265,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final Rect mAbsoluteBounds = new Rect(); boolean mShowWallpaper; int mRotation = ROTATION_UNDEFINED; + @ActivityInfo.Config int mKnownConfigChanges; ChangeInfo(@NonNull WindowContainer origState) { mVisible = origState.isVisibleRequested(); @@ -845,6 +1288,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final boolean currVisible = newState.isVisibleRequested(); if (currVisible == mVisible && !mVisible) return false; return currVisible != mVisible + || mKnownConfigChanges != 0 // if mWindowingMode is 0, this container wasn't attached at collect time, so // assume no change in windowing-mode. || (mWindowingMode != 0 && newState.getWindowingMode() != mWindowingMode) @@ -891,9 +1335,19 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe flags |= FLAG_IS_VOICE_INTERACTION; } } + final DisplayContent dc = wc.asDisplayContent(); + if (dc != null) { + flags |= FLAG_IS_DISPLAY; + if (dc.hasAlertWindowSurfaces()) { + flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS; + } + } if (isWallpaper(wc)) { flags |= FLAG_IS_WALLPAPER; } + if (occludesKeyguard(wc)) { + flags |= FLAG_OCCLUDES_KEYGUARD; + } return flags; } @@ -910,4 +1364,95 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mChildren.addAll(wcs); } } + + /** + * The transition sync mechanism has 2 parts: + * 1. Whether all WM operations for a particular transition are "ready" (eg. did the app + * launch or stop or get a new configuration?). + * 2. Whether all the windows involved have finished drawing their final-state content. + * + * A transition animation can play once both parts are complete. This ready-tracker keeps track + * of part (1). Currently, WM code assumes that "readiness" (part 1) is grouped. This means that + * even if the WM operations in one group are ready, the whole transition itself may not be + * ready if there are WM operations still pending in another group. This class helps keep track + * of readiness across the multiple groups. Currently, we assume that each display is a group + * since that is how it has been until now. + */ + private static class ReadyTracker { + private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>(); + + /** + * Ensures that this doesn't report as allReady before it has been used. This is needed + * in very niche cases where a transition is a no-op (nothing has been collected) but we + * still want to be marked ready (via. setAllReady). + */ + private boolean mUsed = false; + + /** + * If true, this overrides all ready groups and reports ready. Used by shell-initiated + * transitions via {@link #setAllReady()}. + */ + private boolean mReadyOverride = false; + + /** + * Adds a ready-group. Any setReady calls in this subtree will be tracked together. For + * now these are only DisplayContents. + */ + void addGroup(WindowContainer wc) { + if (mReadyGroups.containsKey(wc)) { + Slog.e(TAG, "Trying to add a ready-group twice: " + wc); + return; + } + mReadyGroups.put(wc, false); + } + + /** + * Sets a group's ready state. + * @param wc Any container within a group's subtree. Used to identify the ready-group. + */ + void setReadyFrom(WindowContainer wc, boolean ready) { + mUsed = true; + WindowContainer current = wc; + while (current != null) { + if (isReadyGroup(current)) { + mReadyGroups.put(current, ready); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting Ready-group to" + + " %b. group=%s from %s", ready, current, wc); + break; + } + current = current.getParent(); + } + } + + /** Marks this as ready regardless of individual groups. */ + void setAllReady() { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting allReady override"); + mUsed = true; + mReadyOverride = true; + } + + /** @return true if all tracked subtrees are ready. */ + boolean allReady() { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b " + + "override=%b states=[%s]", mUsed, mReadyOverride, groupsToString()); + if (!mUsed) return false; + if (mReadyOverride) return true; + for (int i = mReadyGroups.size() - 1; i >= 0; --i) { + final WindowContainer wc = mReadyGroups.keyAt(i); + if (!wc.isAttached() || !wc.isVisibleRequested()) continue; + if (!mReadyGroups.valueAt(i)) return false; + } + return true; + } + + private String groupsToString() { + StringBuilder b = new StringBuilder(); + for (int i = 0; i < mReadyGroups.size(); ++i) { + if (i != 0) b.append(','); + b.append(mReadyGroups.keyAt(i)).append(':') + .append(mReadyGroups.valueAt(i)); + } + return b.toString(); + } + } } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index cc63c4922c32..929ec3b929b2 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -16,24 +16,39 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.IApplicationThread; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.RemoteException; +import android.os.SystemClock; +import android.util.ArrayMap; import android.util.Slog; +import android.util.proto.ProtoOutputStream; import android.view.WindowManager; -import android.window.IRemoteTransition; +import android.window.ITransitionMetricsReporter; import android.window.ITransitionPlayer; +import android.window.RemoteTransition; +import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; +import com.android.server.LocalServices; +import com.android.server.statusbar.StatusBarManagerInternal; import java.util.ArrayList; +import java.util.function.LongConsumer; /** * Handles all the aspects of recording and synchronizing transitions. @@ -41,8 +56,25 @@ import java.util.ArrayList; class TransitionController { private static final String TAG = "TransitionController"; + /** The same as legacy APP_TRANSITION_TIMEOUT_MS. */ + private static final int DEFAULT_TIMEOUT_MS = 5000; + /** Less duration for CHANGE type because it does not involve app startup. */ + private static final int CHANGE_TIMEOUT_MS = 2000; + + // State constants to line-up with legacy app-transition proto expectations. + private static final int LEGACY_STATE_IDLE = 0; + private static final int LEGACY_STATE_READY = 1; + private static final int LEGACY_STATE_RUNNING = 2; + private ITransitionPlayer mTransitionPlayer; + final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter(); + + private IApplicationThread mTransitionPlayerThread; final ActivityTaskManagerService mAtm; + final TaskSnapshotController mTaskSnapshotController; + + private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners = + new ArrayList<>(); /** * Currently playing transitions (in the order they were started). When finished, records are @@ -50,20 +82,32 @@ class TransitionController { */ private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>(); - private final IBinder.DeathRecipient mTransitionPlayerDeath = () -> { - // clean-up/finish any playing transitions. - for (int i = 0; i < mPlayingTransitions.size(); ++i) { - mPlayingTransitions.get(i).cleanUpOnFailure(); - } - mPlayingTransitions.clear(); - mTransitionPlayer = null; - }; + final Lock mRunningLock = new Lock(); + + private final IBinder.DeathRecipient mTransitionPlayerDeath; /** The transition currently being constructed (collecting participants). */ private Transition mCollectingTransition = null; - TransitionController(ActivityTaskManagerService atm) { + // TODO(b/188595497): remove when not needed. + final StatusBarManagerInternal mStatusBar; + + TransitionController(ActivityTaskManagerService atm, + TaskSnapshotController taskSnapshotController) { mAtm = atm; + mStatusBar = LocalServices.getService(StatusBarManagerInternal.class); + mTaskSnapshotController = taskSnapshotController; + mTransitionPlayerDeath = () -> { + synchronized (mAtm.mGlobalLock) { + // Clean-up/finish any playing transitions. + for (int i = 0; i < mPlayingTransitions.size(); ++i) { + mPlayingTransitions.get(i).cleanUpOnFailure(); + } + mPlayingTransitions.clear(); + mTransitionPlayer = null; + mRunningLock.doNotifyLocked(); + } + }; } /** @see #createTransition(int, int) */ @@ -76,7 +120,7 @@ class TransitionController { * Creates a transition. It can immediately collect participants. */ @NonNull - Transition createTransition(@WindowManager.TransitionType int type, + private Transition createTransition(@WindowManager.TransitionType int type, @WindowManager.TransitionFlags int flags) { if (mTransitionPlayer == null) { throw new IllegalStateException("Shell Transitions not enabled"); @@ -84,20 +128,31 @@ class TransitionController { if (mCollectingTransition != null) { throw new IllegalStateException("Simultaneous transitions not supported yet."); } - mCollectingTransition = new Transition(type, flags, this, mAtm.mWindowManager.mSyncEngine); + // Distinguish change type because the response time is usually expected to be not too long. + final long timeoutMs = type == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS; + mCollectingTransition = new Transition(type, flags, timeoutMs, this, + mAtm.mWindowManager.mSyncEngine); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s", mCollectingTransition); + dispatchLegacyAppTransitionPending(); return mCollectingTransition; } - void registerTransitionPlayer(@Nullable ITransitionPlayer player) { + void registerTransitionPlayer(@Nullable ITransitionPlayer player, + @Nullable IApplicationThread appThread) { try { + // Note: asBinder() can be null if player is same process (likely in a test). if (mTransitionPlayer != null) { - mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0); + if (mTransitionPlayer.asBinder() != null) { + mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0); + } mTransitionPlayer = null; } - player.asBinder().linkToDeath(mTransitionPlayerDeath, 0); + if (player.asBinder() != null) { + player.asBinder().linkToDeath(mTransitionPlayerDeath, 0); + } mTransitionPlayer = player; + mTransitionPlayerThread = appThread; } catch (RemoteException e) { throw new RuntimeException("Unable to set transition player"); } @@ -155,21 +210,42 @@ class TransitionController { } /** - * @see #requestTransitionIfNeeded(int, int, WindowContainer, IRemoteTransition) + * @return {@code true} if {@param ar} is part of a transient-launch activity in an active + * transition. + */ + boolean isTransientLaunch(@NonNull ActivityRecord ar) { + if (mCollectingTransition != null && mCollectingTransition.isTransientLaunch(ar)) { + return true; + } + for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { + if (mPlayingTransitions.get(i).isTransientLaunch(ar)) return true; + } + return false; + } + + @WindowManager.TransitionType + int getCollectingTransitionType() { + return mCollectingTransition != null ? mCollectingTransition.mType : TRANSIT_NONE; + } + + /** + * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition) */ @Nullable Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type, - @Nullable WindowContainer trigger) { - return requestTransitionIfNeeded(type, 0 /* flags */, trigger); + @NonNull WindowContainer trigger) { + return requestTransitionIfNeeded(type, 0 /* flags */, trigger, trigger /* readyGroupRef */); } /** - * @see #requestTransitionIfNeeded(int, int, WindowContainer, IRemoteTransition) + * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition) */ @Nullable Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type, - @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger) { - return requestTransitionIfNeeded(type, flags, trigger, null /* remote */); + @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger, + @NonNull WindowContainer readyGroupRef) { + return requestTransitionIfNeeded(type, flags, trigger, readyGroupRef, + null /* remoteTransition */); } private static boolean isExistenceType(@WindowManager.TransitionType int type) { @@ -180,19 +256,24 @@ class TransitionController { * If a transition isn't requested yet, creates one and asks the TransitionPlayer (Shell) to * start it. Collection can start immediately. * @param trigger if non-null, this is the first container that will be collected + * @param readyGroupRef Used to identify which ready-group this request is for. * @return the created transition if created or null otherwise. */ @Nullable Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type, @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger, - @Nullable IRemoteTransition remoteTransition) { + @NonNull WindowContainer readyGroupRef, @Nullable RemoteTransition remoteTransition) { if (mTransitionPlayer == null) { return null; } Transition newTransition = null; if (isCollecting()) { // Make the collecting transition wait until this request is ready. - mCollectingTransition.setReady(false); + mCollectingTransition.setReady(readyGroupRef, false); + if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) { + // Add keyguard flag to dismiss keyguard + mCollectingTransition.addFlag(flags); + } } else { newTransition = requestStartTransition(createTransition(type, flags), trigger != null ? trigger.asTask() : null, remoteTransition); @@ -210,7 +291,7 @@ class TransitionController { /** Asks the transition player (shell) to start a created but not yet started transition. */ @NonNull Transition requestStartTransition(@NonNull Transition transition, @Nullable Task startTask, - @Nullable IRemoteTransition remoteTransition) { + @Nullable RemoteTransition remoteTransition) { try { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Requesting StartTransition: %s", transition); @@ -228,6 +309,22 @@ class TransitionController { return transition; } + /** Requests transition for a window container which will be removed or invisible. */ + void requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) { + if (mTransitionPlayer == null) return; + if (wc.isVisibleRequested()) { + if (!isCollecting()) { + requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */), + wc.asTask(), null /* remoteTransition */); + } + collectExistenceChange(wc); + } else { + // Removing a non-visible window doesn't require a transition, but if there is one + // collecting, this should be a member just in case. + collect(wc); + } + } + /** @see Transition#collect */ void collect(@NonNull WindowContainer wc) { if (mCollectingTransition == null) return; @@ -240,19 +337,28 @@ class TransitionController { mCollectingTransition.collectExistenceChange(wc); } + /** @see Transition#setOverrideAnimation */ + void setOverrideAnimation(TransitionInfo.AnimationOptions options, + @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) { + if (mCollectingTransition == null) return; + mCollectingTransition.setOverrideAnimation(options, startCallback, finishCallback); + } + /** @see Transition#setReady */ - void setReady(boolean ready) { + void setReady(WindowContainer wc, boolean ready) { if (mCollectingTransition == null) return; - mCollectingTransition.setReady(ready); + mCollectingTransition.setReady(wc, ready); } /** @see Transition#setReady */ - void setReady() { - setReady(true); + void setReady(WindowContainer wc) { + setReady(wc, true); } /** @see Transition#finishTransition */ void finishTransition(@NonNull IBinder token) { + // It is usually a no-op but make sure that the metric consumer is removed. + mTransitionMetricsReporter.reportAnimationStart(token, 0 /* startTime */); final Transition record = Transition.fromBinder(token); if (record == null || !mPlayingTransitions.contains(record)) { Slog.e(TAG, "Trying to finish a non-playing transition " + token); @@ -260,7 +366,11 @@ class TransitionController { } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record); mPlayingTransitions.remove(record); + if (mPlayingTransitions.isEmpty()) { + setAnimationRunning(false /* running */); + } record.finishTransition(); + mRunningLock.doNotifyLocked(); } void moveToPlaying(Transition transition) { @@ -268,9 +378,22 @@ class TransitionController { throw new IllegalStateException("Trying to move non-collecting transition to playing"); } mCollectingTransition = null; + if (mPlayingTransitions.isEmpty()) { + setAnimationRunning(true /* running */); + } mPlayingTransitions.add(transition); } + private void setAnimationRunning(boolean running) { + if (mTransitionPlayerThread == null) return; + final WindowProcessController wpc = mAtm.getProcessController(mTransitionPlayerThread); + if (wpc == null) { + Slog.w(TAG, "Unable to find process for player thread=" + mTransitionPlayerThread); + return; + } + wpc.setRunningRemoteAnimation(running); + } + void abort(Transition transition) { if (transition != mCollectingTransition) { throw new IllegalStateException("Too late to abort."); @@ -279,4 +402,137 @@ class TransitionController { mCollectingTransition = null; } + /** + * Record that the launch of {@param activity} is transient (meaning its lifecycle is currently + * tied to the transition). + */ + void setTransientLaunch(@NonNull ActivityRecord activity) { + if (mCollectingTransition == null) return; + mCollectingTransition.setTransientLaunch(activity); + + // TODO(b/188669821): Remove once legacy recents behavior is moved to shell. + // Also interpret HOME transient launch as recents + if (activity.getActivityType() == ACTIVITY_TYPE_HOME) { + mCollectingTransition.addFlag(TRANSIT_FLAG_IS_RECENTS); + } + } + + void legacyDetachNavigationBarFromApp(@NonNull IBinder token) { + final Transition transition = Transition.fromBinder(token); + if (transition == null || !mPlayingTransitions.contains(transition)) { + Slog.e(TAG, "Transition isn't playing: " + token); + return; + } + transition.legacyRestoreNavigationBarFromApp(); + } + + void registerLegacyListener(WindowManagerInternal.AppTransitionListener listener) { + mLegacyListeners.add(listener); + } + + void unregisterLegacyListener(WindowManagerInternal.AppTransitionListener listener) { + mLegacyListeners.remove(listener); + } + + void dispatchLegacyAppTransitionPending() { + for (int i = 0; i < mLegacyListeners.size(); ++i) { + mLegacyListeners.get(i).onAppTransitionPendingLocked(); + } + } + + void dispatchLegacyAppTransitionStarting(TransitionInfo info) { + final boolean keyguardGoingAway = info.isKeyguardGoingAway(); + for (int i = 0; i < mLegacyListeners.size(); ++i) { + // TODO(shell-transitions): handle (un)occlude transition. + mLegacyListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway, + false /* keyguardOcclude */, 0 /* durationHint */, + SystemClock.uptimeMillis(), AnimationAdapter.STATUS_BAR_TRANSITION_DURATION); + } + } + + void dispatchLegacyAppTransitionFinished(ActivityRecord ar) { + for (int i = 0; i < mLegacyListeners.size(); ++i) { + mLegacyListeners.get(i).onAppTransitionFinishedLocked(ar.token); + } + } + + void dispatchLegacyAppTransitionCancelled() { + for (int i = 0; i < mLegacyListeners.size(); ++i) { + mLegacyListeners.get(i).onAppTransitionCancelledLocked( + false /* keyguardGoingAway */); + } + } + + void dumpDebugLegacy(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + int state = LEGACY_STATE_IDLE; + if (!mPlayingTransitions.isEmpty()) { + state = LEGACY_STATE_RUNNING; + } else if (mCollectingTransition != null && mCollectingTransition.getLegacyIsReady()) { + state = LEGACY_STATE_READY; + } + proto.write(AppTransitionProto.APP_TRANSITION_STATE, state); + proto.end(token); + } + + static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub { + private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>(); + + void associate(IBinder transitionToken, LongConsumer consumer) { + synchronized (mMetricConsumers) { + mMetricConsumers.put(transitionToken, consumer); + } + } + + @Override + public void reportAnimationStart(IBinder transitionToken, long startTime) { + final LongConsumer c; + synchronized (mMetricConsumers) { + if (mMetricConsumers.isEmpty()) return; + c = mMetricConsumers.remove(transitionToken); + } + if (c != null) { + c.accept(startTime); + } + } + } + + class Lock { + private int mTransitionWaiters = 0; + void runWhenIdle(long timeout, Runnable r) { + synchronized (mAtm.mGlobalLock) { + if (!inTransition()) { + r.run(); + return; + } + mTransitionWaiters += 1; + } + final long startTime = SystemClock.uptimeMillis(); + final long endTime = startTime + timeout; + while (true) { + synchronized (mAtm.mGlobalLock) { + if (!inTransition() || SystemClock.uptimeMillis() > endTime) { + mTransitionWaiters -= 1; + r.run(); + return; + } + } + synchronized (this) { + try { + this.wait(timeout); + } catch (InterruptedException e) { + return; + } + } + } + } + + void doNotifyLocked() { + synchronized (this) { + if (mTransitionWaiters > 0) { + this.notifyAll(); + } + } + } + } } diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java index 416b9dfe50b4..4a5a20e57804 100644 --- a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java @@ -20,6 +20,7 @@ import static com.android.server.wm.AnimationAdapterProto.REMOTE; import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; +import android.annotation.NonNull; import android.graphics.Point; import android.os.SystemClock; import android.util.proto.ProtoOutputStream; @@ -64,18 +65,17 @@ class WallpaperAnimationAdapter implements AnimationAdapter { * * @return RemoteAnimationTarget[] targets for all the visible wallpaper windows */ - public static RemoteAnimationTarget[] startWallpaperAnimations(WindowManagerService service, + public static RemoteAnimationTarget[] startWallpaperAnimations(DisplayContent displayContent, long durationHint, long statusBarTransitionDelay, Consumer<WallpaperAnimationAdapter> animationCanceledRunnable, ArrayList<WallpaperAnimationAdapter> adaptersOut) { + if (!shouldStartWallpaperAnimation(displayContent)) { + ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, + "\tWallpaper of display=%s is not visible", displayContent); + return new RemoteAnimationTarget[0]; + } final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); - service.mRoot.forAllWallpaperWindows(wallpaperWindow -> { - if (!wallpaperWindow.getDisplayContent().mWallpaperController.isWallpaperVisible()) { - ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tNot visible=%s", wallpaperWindow); - return; - } - - ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tvisible=%s", wallpaperWindow); + displayContent.forAllWallpaperWindows(wallpaperWindow -> { final WallpaperAnimationAdapter wallpaperAdapter = new WallpaperAnimationAdapter( wallpaperWindow, durationHint, statusBarTransitionDelay, animationCanceledRunnable); @@ -87,13 +87,17 @@ class WallpaperAnimationAdapter implements AnimationAdapter { return targets.toArray(new RemoteAnimationTarget[targets.size()]); } + static boolean shouldStartWallpaperAnimation(DisplayContent displayContent) { + return displayContent.mWallpaperController.isWallpaperVisible(); + } + /** * Create a remote animation target for this animation adapter. */ RemoteAnimationTarget createRemoteAnimationTarget() { mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false, null, null, mWallpaperToken.getPrefixOrderIndex(), new Point(), null, null, - mWallpaperToken.getWindowConfiguration(), true, null, null, null); + mWallpaperToken.getWindowConfiguration(), true, null, null, null, false); return mTarget; } @@ -134,7 +138,8 @@ class WallpaperAnimationAdapter implements AnimationAdapter { @Override public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, - @AnimationType int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, + @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation"); // Restore z-layering until client has a chance to modify it. diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 1f2a9a29932c..4b2aa0f76272 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -16,18 +16,20 @@ package com.android.server.wm; +import static android.app.WallpaperManager.COMMAND_FREEZE; +import static android.app.WallpaperManager.COMMAND_UNFREEZE; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT; @@ -47,6 +49,8 @@ import android.view.WindowManager; import android.view.animation.Animation; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.ProtoLogImpl; +import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ToBooleanFunction; import java.io.PrintWriter; @@ -79,6 +83,8 @@ class WallpaperController { private int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE; private int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE; private final float mMaxWallpaperScale; + // Whether COMMAND_FREEZE was dispatched. + private boolean mLastFrozen = false; // This is set when we are waiting for a wallpaper to tell us it is done // changing its scroll position. @@ -124,7 +130,7 @@ class WallpaperController { } mFindResults.resetTopWallpaper = true; - if (mService.mAtmService.getTransitionController().getTransitionPlayer() == null) { + if (!w.mTransitionController.isShellTransitionsEnabled()) { if (w.mActivityRecord != null && !w.mActivityRecord.isVisible() && !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) { // If this window's app token is hidden and not animating, it is of no interest. @@ -195,6 +201,7 @@ class WallpaperController { if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": token animating, looking behind."); } + mFindResults.setIsWallpaperTargetForLetterbox(w.hasWallpaperForLetterboxBackground()); // Found a target! End search. return true; } @@ -287,10 +294,11 @@ class WallpaperController { for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) { final WallpaperWindowToken token = mWallpaperTokens.get(i); token.setVisibility(false); - if (DEBUG_WALLPAPER_LIGHT && token.isVisible()) { - Slog.d(TAG, "Hiding wallpaper " + token - + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev=" - + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, " ")); + if (ProtoLogImpl.isEnabled(WM_DEBUG_WALLPAPER) && token.isVisible()) { + ProtoLog.d(WM_DEBUG_WALLPAPER, + "Hiding wallpaper %s from %s target=%s prev=%s callers=%s", + token, winGoingAway, mWallpaperTarget, mPrevWallpaperTarget, + Debug.getCallers(5)); } } } @@ -425,20 +433,25 @@ class WallpaperController { Bundle sendWindowWallpaperCommand( WindowState window, String action, int x, int y, int z, Bundle extras, boolean sync) { if (window == mWallpaperTarget || window == mPrevWallpaperTarget) { - boolean doWait = sync; - for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { - final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx); - token.sendWindowWallpaperCommand(action, x, y, z, extras, sync); - } - - if (doWait) { - // TODO: Need to wait for result. - } + sendWindowWallpaperCommand(action, x, y, z, extras, sync); } return null; } + private void sendWindowWallpaperCommand( + String action, int x, int y, int z, Bundle extras, boolean sync) { + boolean doWait = sync; + for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { + final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx); + token.sendWindowWallpaperCommand(action, x, y, z, extras, sync); + } + + if (doWait) { + // TODO: Need to wait for result. + } + } + private void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) { WindowState target = mWallpaperTarget; if (target != null) { @@ -535,15 +548,15 @@ class WallpaperController { // Is it time to stop animating? if (!mPrevWallpaperTarget.isAnimatingLw()) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "No longer animating wallpaper targets!"); + ProtoLog.v(WM_DEBUG_WALLPAPER, "No longer animating wallpaper targets!"); mPrevWallpaperTarget = null; mWallpaperTarget = wallpaperTarget; } return; } - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "New wallpaper target: " + wallpaperTarget + " prevTarget: " + mWallpaperTarget); + ProtoLog.v(WM_DEBUG_WALLPAPER, "New wallpaper target: %s prevTarget: %s caller=%s", + wallpaperTarget, mWallpaperTarget, Debug.getCallers(5)); mPrevWallpaperTarget = null; @@ -561,8 +574,8 @@ class WallpaperController { // then we are in our super special mode! boolean oldAnim = prevWallpaperTarget.isAnimatingLw(); boolean foundAnim = wallpaperTarget.isAnimatingLw(); - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "New animation: " + foundAnim + " old animation: " + oldAnim); + ProtoLog.v(WM_DEBUG_WALLPAPER, "New animation: %s old animation: %s", + foundAnim, oldAnim); if (!foundAnim || !oldAnim) { return; @@ -577,14 +590,14 @@ class WallpaperController { final boolean oldTargetHidden = prevWallpaperTarget.mActivityRecord != null && !prevWallpaperTarget.mActivityRecord.mVisibleRequested; - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Animating wallpapers:" + " old: " - + prevWallpaperTarget + " hidden=" + oldTargetHidden + " new: " + wallpaperTarget - + " hidden=" + newTargetHidden); + ProtoLog.v(WM_DEBUG_WALLPAPER, "Animating wallpapers: " + + "old: %s hidden=%b new: %s hidden=%b", + prevWallpaperTarget, oldTargetHidden, wallpaperTarget, newTargetHidden); mPrevWallpaperTarget = prevWallpaperTarget; if (newTargetHidden && !oldTargetHidden) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Old wallpaper still the target."); + ProtoLog.v(WM_DEBUG_WALLPAPER, "Old wallpaper still the target."); // Use the old target if new target is hidden but old target // is not. If they're both hidden, still use the new target. mWallpaperTarget = prevWallpaperTarget; @@ -645,8 +658,15 @@ class WallpaperController { updateWallpaperTokens(visible); - if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, "New wallpaper: target=" + mWallpaperTarget - + " prev=" + mPrevWallpaperTarget); + if (visible && mLastFrozen != mFindResults.isWallpaperTargetForLetterbox) { + mLastFrozen = mFindResults.isWallpaperTargetForLetterbox; + sendWindowWallpaperCommand( + mFindResults.isWallpaperTargetForLetterbox ? COMMAND_FREEZE : COMMAND_UNFREEZE, + /* x= */ 0, /* y= */ 0, /* z= */ 0, /* extras= */ null, /* sync= */ false); + } + + ProtoLog.d(WM_DEBUG_WALLPAPER, "New wallpaper: target=%s prev=%s", + mWallpaperTarget, mPrevWallpaperTarget); } boolean processWallpaperDrawPendingTimeout() { @@ -782,6 +802,18 @@ class WallpaperController { wallpaperBuffer.getHardwareBuffer(), wallpaperBuffer.getColorSpace()); } + /** + * Mirrors the visible wallpaper if it's available. + * + * @return A SurfaceControl for the parent of the mirrored wallpaper. + */ + SurfaceControl mirrorWallpaperSurface() { + final WindowState wallpaperWindowState = getTopVisibleWallpaper(); + return wallpaperWindowState != null + ? SurfaceControl.mirrorSurface(wallpaperWindowState.getSurfaceControl()) + : null; + } + WindowState getTopVisibleWallpaper() { mTmpTopWallpaper = null; @@ -842,6 +874,7 @@ class WallpaperController { boolean useTopWallpaperAsTarget = false; WindowState wallpaperTarget = null; boolean resetTopWallpaper = false; + boolean isWallpaperTargetForLetterbox = false; void setTopWallpaper(WindowState win) { topWallpaper = win; @@ -855,11 +888,16 @@ class WallpaperController { useTopWallpaperAsTarget = topWallpaperAsTarget; } + void setIsWallpaperTargetForLetterbox(boolean isWallpaperTargetForLetterbox) { + this.isWallpaperTargetForLetterbox = isWallpaperTargetForLetterbox; + } + void reset() { topWallpaper = null; wallpaperTarget = null; useTopWallpaperAsTarget = false; resetTopWallpaper = false; + isWallpaperTargetForLetterbox = false; } } } diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index b54e8b7a7b4e..3a639f50603f 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -20,7 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -28,7 +28,6 @@ import android.annotation.Nullable; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; -import android.util.Slog; import android.view.DisplayInfo; import android.view.ViewGroup; import android.view.WindowManager; @@ -107,12 +106,12 @@ class WallpaperWindowToken extends WindowToken { void updateWallpaperWindows(boolean visible) { if (isVisible() != visible) { - if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, - "Wallpaper token " + token + " visible=" + visible); + ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b", + token, visible); setVisibility(visible); } final WallpaperController wallpaperController = mDisplayContent.mWallpaperController; - if (mWmService.mAtmService.getTransitionController().getTransitionPlayer() != null) { + if (mTransitionController.isShellTransitionsEnabled()) { return; } @@ -157,12 +156,12 @@ class WallpaperWindowToken extends WindowToken { */ void setVisibility(boolean visible) { // Before setting mVisibleRequested so we can track changes. - mWmService.mAtmService.getTransitionController().collect(this); + mTransitionController.collect(this); setVisibleRequested(visible); // If in a transition, defer commits for activities that are going invisible - if (!visible && (mWmService.mAtmService.getTransitionController().inTransition() + if (!visible && (mTransitionController.inTransition() || getDisplayContent().mAppTransition.isRunning())) { return; } diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java index 66ab094f0994..9780d3317e11 100644 --- a/services/core/java/com/android/server/wm/Watermark.java +++ b/services/core/java/com/android/server/wm/Watermark.java @@ -31,6 +31,7 @@ import android.util.TypedValue; import android.view.Surface; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl; +import android.view.WindowManagerPolicyConstants; /** * Displays a watermark on top of the window manager's windows. @@ -117,7 +118,7 @@ class Watermark { .setFormat(PixelFormat.TRANSLUCENT) .setCallsite(TITLE) .build(); - t.setLayer(ctrl, WindowManagerService.TYPE_LAYER_MULTIPLIER * 100) + t.setLayer(ctrl, WindowManagerPolicyConstants.WATERMARK_LAYER) .setPosition(ctrl, 0, 0) .show(ctrl); // Ensure we aren't considered as obscuring for Input purposes. diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index eb32486d6023..4a43f4f73eda 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -164,7 +164,7 @@ public class WindowAnimator { final DisplayContent dc = root.getDisplayContent(displayId); dc.checkAppWindowsReadyToShow(); - if (accessibilityController != null) { + if (accessibilityController.hasCallbacks()) { accessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId, mTransaction); } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index d99aed1b409a..5af9147cd56f 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; @@ -30,10 +31,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.os.UserHandle.USER_NULL; import static android.view.SurfaceControl.Transaction; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; -import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE; -import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; -import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK; -import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT; +import static android.view.WindowManager.TRANSIT_CHANGE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; @@ -41,6 +39,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION; +import static com.android.server.wm.AppTransition.isTaskTransitOld; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; @@ -55,6 +54,7 @@ import static com.android.server.wm.WindowContainerProto.CONFIGURATION_CONTAINER import static com.android.server.wm.WindowContainerProto.IDENTIFIER; import static com.android.server.wm.WindowContainerProto.ORIENTATION; import static com.android.server.wm.WindowContainerProto.SURFACE_ANIMATOR; +import static com.android.server.wm.WindowContainerProto.SURFACE_CONTROL; import static com.android.server.wm.WindowContainerProto.VISIBLE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -67,8 +67,6 @@ import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityThread; -import android.app.WindowConfiguration; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -89,6 +87,7 @@ import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.view.SurfaceControl.Builder; import android.view.SurfaceSession; +import android.view.TaskTransitionSpec; import android.view.WindowManager; import android.view.WindowManager.TransitionOldType; import android.view.animation.Animation; @@ -109,6 +108,8 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedList; import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -125,26 +126,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM; - /** Animation layer that happens above all animating {@link Task}s. */ - static final int ANIMATION_LAYER_STANDARD = 0; - - /** Animation layer that happens above all {@link Task}s. */ - static final int ANIMATION_LAYER_BOOSTED = 1; - - /** - * Animation layer that is reserved for {@link WindowConfiguration#ACTIVITY_TYPE_HOME} - * activities and all activities that are being controlled by the recents animation. This - * layer is generally below all {@link Task}s. - */ - static final int ANIMATION_LAYER_HOME = 2; - - @IntDef(prefix = { "ANIMATION_LAYER_" }, value = { - ANIMATION_LAYER_STANDARD, - ANIMATION_LAYER_BOOSTED, - ANIMATION_LAYER_HOME, - }) - @interface AnimationLayer {} - static final int POSITION_TOP = Integer.MAX_VALUE; static final int POSITION_BOTTOM = Integer.MIN_VALUE; @@ -195,10 +176,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * Applied as part of the animation pass in "prepareSurfaces". */ protected final SurfaceAnimator mSurfaceAnimator; - private boolean mAnyParentAnimating; + + /** The parent leash added for animation. */ + @Nullable + private SurfaceControl mAnimationLeash; final SurfaceFreezer mSurfaceFreezer; protected final WindowManagerService mWmService; + final TransitionController mTransitionController; /** * Sources which triggered a surface animation on this container. An animation target can be @@ -329,6 +314,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< WindowContainer(WindowManagerService wms) { mWmService = wms; + mTransitionController = mWmService.mAtmService.getTransitionController(); mPendingTransaction = wms.mTransactionFactory.get(); mSyncTransaction = wms.mTransactionFactory.get(); mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms); @@ -362,6 +348,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< throw new IllegalArgumentException("reparent: can't reparent to null " + this); } + if (newParent == this) { + throw new IllegalArgumentException("Can not reparent to itself " + this); + } + final WindowContainer oldParent = mParent; if (mParent == newParent) { throw new IllegalArgumentException("WC=" + this + " already child of " + mParent); @@ -624,7 +614,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< final DisplayContent dc = getDisplayContent(); if (dc != null) { mSurfaceFreezer.unfreeze(getSyncTransaction()); - dc.mChangingContainers.remove(this); } while (!mChildren.isEmpty()) { final E child = mChildren.peekLast(); @@ -865,7 +854,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } boolean isAttached() { - return getDisplayArea() != null; + WindowContainer parent = getParent(); + return parent != null && parent.isAttached(); } void setWaitingForDrawnIfResizingChanged() { @@ -1000,6 +990,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return mDisplayContent != null && mDisplayContent.mChangingContainers.contains(this); } + boolean inTransition() { + return mTransitionController.inTransition(this); + } + void sendAppVisibilityToClients() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); @@ -1072,9 +1066,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // part of a change transition. if (!visible) { mSurfaceFreezer.unfreeze(getSyncTransaction()); - if (mDisplayContent != null) { - mDisplayContent.mChangingContainers.remove(this); - } } WindowContainer parent = getParent(); if (parent != null) { @@ -1389,6 +1380,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } + /** Returns whether the window should be shown for current user. */ boolean showToCurrentUser() { return true; } @@ -1685,6 +1677,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return false; } + boolean forAllLeafTaskFragments(Function<TaskFragment, Boolean> callback) { + for (int i = mChildren.size() - 1; i >= 0; --i) { + if (mChildren.get(i).forAllLeafTaskFragments(callback)) { + return true; + } + } + return false; + } + /** * For all root tasks at or below this container call the callback. * @@ -1740,6 +1741,28 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } + /** + * For all task fragments at or below this container call the callback. + * + * @param callback Callback to be called for every task. + */ + void forAllTaskFragments(Consumer<TaskFragment> callback) { + forAllTaskFragments(callback, true /*traverseTopToBottom*/); + } + + void forAllTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) { + final int count = mChildren.size(); + if (traverseTopToBottom) { + for (int i = count - 1; i >= 0; --i) { + mChildren.get(i).forAllTaskFragments(callback, traverseTopToBottom); + } + } else { + for (int i = 0; i < count; i++) { + mChildren.get(i).forAllTaskFragments(callback, traverseTopToBottom); + } + } + } + void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) { final int count = mChildren.size(); if (traverseTopToBottom) { @@ -1753,6 +1776,19 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } + void forAllLeafTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) { + final int count = mChildren.size(); + if (traverseTopToBottom) { + for (int i = count - 1; i >= 0; --i) { + mChildren.get(i).forAllLeafTaskFragments(callback, traverseTopToBottom); + } + } else { + for (int i = 0; i < count; i++) { + mChildren.get(i).forAllLeafTaskFragments(callback, traverseTopToBottom); + } + } + } + /** * For all root tasks at or below this container call the callback. * @@ -2254,7 +2290,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< void assignLayer(Transaction t, int layer) { // Don't assign layers while a transition animation is playing // TODO(b/173528115): establish robust best-practices around z-order fighting. - if (mWmService.mAtmService.getTransitionController().isPlaying()) return; + if (mTransitionController.isPlaying()) return; final boolean changed = layer != mLastLayer || mLastRelativeToLayer != null; if (mSurfaceControl != null && changed) { setLayer(t, layer); @@ -2278,10 +2314,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } protected void setLayer(Transaction t, int layer) { - - // Route through surface animator to accommodate that our surface control might be - // attached to the leash, and leash is attached to parent container. - mSurfaceAnimator.setLayer(t, layer); + if (mSurfaceFreezer.hasLeash()) { + // When the freezer has created animation leash parent for the window, set the layer + // there instead. + mSurfaceFreezer.setLayer(t, layer); + } else { + // Route through surface animator to accommodate that our surface control might be + // attached to the leash, and leash is attached to parent container. + mSurfaceAnimator.setLayer(t, layer); + } } int getLastLayer() { @@ -2289,10 +2330,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) { - - // Route through surface animator to accommodate that our surface control might be - // attached to the leash, and leash is attached to parent container. - mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer); + if (mSurfaceFreezer.hasLeash()) { + // When the freezer has created animation leash parent for the window, set the layer + // there instead. + mSurfaceFreezer.setRelativeLayer(t, relativeTo, layer); + } else { + // Route through surface animator to accommodate that our surface control might be + // attached to the leash, and leash is attached to parent container. + mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer); + } } protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) { @@ -2362,6 +2408,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< if (mSurfaceAnimator.isAnimating()) { mSurfaceAnimator.dumpDebug(proto, SURFACE_ANIMATOR); } + if (mSurfaceControl != null) { + mSurfaceControl.dumpDebug(proto, SURFACE_CONTROL); + } // add children to proto for (int i = 0; i < getChildCount(); i++) { @@ -2511,11 +2560,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @param animationFinishedCallback The callback being triggered when the animation finishes. * @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a * cancel call to the underlying AnimationAdapter. + * @param snapshotAnim The animation to run for the snapshot. {@code null} if there is no + * snapshot. */ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, @AnimationType int type, @Nullable OnAnimationFinishedCallback animationFinishedCallback, - @Nullable Runnable animationCancelledCallback) { + @Nullable Runnable animationCancelledCallback, + @Nullable AnimationAdapter snapshotAnim) { if (DEBUG_ANIM) { Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + anim); } @@ -2523,14 +2575,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // TODO: This should use isVisible() but because isVisible has a really weird meaning at // the moment this doesn't work for all animatable window containers. mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback, - animationCancelledCallback, mSurfaceFreezer); + animationCancelledCallback, snapshotAnim, mSurfaceFreezer); } void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, @AnimationType int type, @Nullable OnAnimationFinishedCallback animationFinishedCallback) { startAnimation(t, anim, hidden, type, animationFinishedCallback, - null /* adapterAnimationCancelledCallback */); + null /* adapterAnimationCancelledCallback */, null /* snapshotAnim */); } void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, @@ -2548,13 +2600,61 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mSurfaceFreezer.unfreeze(getPendingTransaction()); } + /** Whether we can start change transition with this window and current display status. */ + boolean canStartChangeTransition() { + return !mWmService.mDisableTransitionAnimation && mDisplayContent != null + && getSurfaceControl() != null && !mDisplayContent.inTransition() + && isVisible() && isVisibleRequested() && okToAnimate(); + } + + /** + * Initializes a change transition. See {@link SurfaceFreezer} for more information. + * + * For now, this will only be called for the following cases: + * 1. {@link Task} is changing windowing mode between fullscreen and freeform. + * 2. {@link TaskFragment} is organized and is changing window bounds. + * 3. {@link ActivityRecord} is reparented into an organized {@link TaskFragment}. (The + * transition will happen on the {@link TaskFragment} for this case). + * + * This shouldn't be called on other {@link WindowContainer} unless there is a valid + * use case. + * + * @param startBounds The original bounds (on screen) of the surface we are snapshotting. + * @param freezeTarget The surface to take snapshot from. If {@code null}, we will take a + * snapshot from {@link #getFreezeSnapshotTarget()}. + */ + void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) { + mDisplayContent.prepareAppTransition(TRANSIT_CHANGE); + mDisplayContent.mChangingContainers.add(this); + // Calculate the relative position in parent container. + final Rect parentBounds = getParent().getBounds(); + mTmpPoint.set(startBounds.left - parentBounds.left, startBounds.top - parentBounds.top); + mSurfaceFreezer.freeze(getSyncTransaction(), startBounds, mTmpPoint, freezeTarget); + } + + void initializeChangeTransition(Rect startBounds) { + initializeChangeTransition(startBounds, null /* freezeTarget */); + } + ArraySet<WindowContainer> getAnimationSources() { return mSurfaceAnimationSources; } @Override public SurfaceControl getFreezeSnapshotTarget() { - return null; + // Only allow freezing if this window is in a TRANSIT_CHANGE + if (!mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CHANGE) + || !mDisplayContent.mChangingContainers.contains(this)) { + return null; + } + return getSurfaceControl(); + } + + @Override + public void onUnfrozen() { + if (mDisplayContent != null) { + mDisplayContent.mChangingContainers.remove(this); + } } @Override @@ -2567,17 +2667,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return getParentSurfaceControl(); } - /** - * @return The layer on which all app animations are happening. - */ - SurfaceControl getAppAnimationLayer(@AnimationLayer int animationLayer) { - final WindowContainer parent = getParent(); - if (parent != null) { - return parent.getAppAnimationLayer(animationLayer); - } - return null; - } - // TODO: Remove this and use #getBounds() instead once we set an app transition animation // on TaskStack. Rect getAnimationBounds(int appRootTaskClipMode) { @@ -2653,6 +2742,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // Separate position and size for use in animators. final Rect screenBounds = getAnimationBounds(appRootTaskClipMode); mTmpRect.set(screenBounds); + if (this.asTask() != null && isTaskTransitOld(transit)) { + this.asTask().adjustAnimationBoundsForTransition(mTmpRect); + } getAnimationPosition(mTmpPoint); mTmpRect.offsetTo(0, 0); @@ -2668,6 +2760,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< final RemoteAnimationController.RemoteAnimationRecord adapters = controller.createRemoteAnimationRecord(this, mTmpPoint, localBounds, screenBounds, (isChanging ? mSurfaceFreezer.mFreezeBounds : null)); + if (!isChanging) { + adapters.setMode(enter + ? RemoteAnimationTarget.MODE_OPENING + : RemoteAnimationTarget.MODE_CLOSING); + } resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter); } else if (isChanging) { final float durationScale = mWmService.getTransitionAnimationScaleLocked(); @@ -2739,40 +2836,37 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mSurfaceAnimationSources.addAll(sources); } - TaskDisplayArea taskDisplayArea = getTaskDisplayArea(); - boolean isSettingBackgroundColor = taskDisplayArea != null - && isTransitionWithBackgroundColor(transit); - - if (isSettingBackgroundColor) { - Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext(); - @ColorInt int backgroundColor = uiContext.getColor(R.color.overview_background); + AnimationRunnerBuilder animationRunnerBuilder = new AnimationRunnerBuilder(); - taskDisplayArea.setBackgroundColor(backgroundColor); + if (isTaskTransitOld(transit)) { + animationRunnerBuilder.setTaskBackgroundColor(getTaskAnimationBackgroundColor()); + // TODO: Remove when we migrate to shell (b/202383002) + if (mWmService.mTaskTransitionSpec != null) { + animationRunnerBuilder.hideInsetSourceViewOverflows( + mWmService.mTaskTransitionSpec.animationBoundInsets); + } } - final Runnable cleanUpCallback = isSettingBackgroundColor - ? taskDisplayArea::clearBackgroundColor : () -> {}; - - startAnimation(getPendingTransaction(), adapter, !isVisible(), - ANIMATION_TYPE_APP_TRANSITION, - (type, anim) -> cleanUpCallback.run(), - cleanUpCallback); + animationRunnerBuilder.build() + .startAnimation(getPendingTransaction(), adapter, !isVisible(), + ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter); if (adapter.getShowWallpaper()) { getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } - if (thumbnailAdapter != null) { - mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(), - thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> { }); - } } } - private boolean isTransitionWithBackgroundColor(@TransitionOldType int transit) { - return transit == TRANSIT_OLD_TASK_OPEN - || transit == TRANSIT_OLD_TASK_CLOSE - || transit == TRANSIT_OLD_TASK_TO_FRONT - || transit == TRANSIT_OLD_TASK_TO_BACK; + private @ColorInt int getTaskAnimationBackgroundColor() { + Context uiContext = mDisplayContent.getDisplayPolicy().getSystemUiContext(); + TaskTransitionSpec customSpec = mWmService.mTaskTransitionSpec; + @ColorInt int defaultFallbackColor = uiContext.getColor(R.color.overview_background); + + if (customSpec != null && customSpec.backgroundColor != 0) { + return customSpec.backgroundColor; + } + + return defaultFallbackColor; } final SurfaceAnimationRunner getSurfaceAnimationRunner() { @@ -2783,7 +2877,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< boolean isVoiceInteraction) { if (isOrganized() // TODO(b/161711458): Clean-up when moved to shell. - && getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { + && getWindowingMode() != WINDOWING_MODE_FULLSCREEN + && getWindowingMode() != WINDOWING_MODE_FREEFORM) { return null; } @@ -2846,18 +2941,21 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return false; } + /** + * {@code true} to indicate that this container can be a candidate of + * {@link AppTransitionController#getAnimationTargets(ArraySet, ArraySet, boolean) animation + * target}. */ + boolean canBeAnimationTarget() { + return false; + } + boolean okToDisplay() { final DisplayContent dc = getDisplayContent(); return dc != null && dc.okToDisplay(); } boolean okToAnimate() { - return okToAnimate(false /* ignoreFrozen */); - } - - boolean okToAnimate(boolean ignoreFrozen) { - final DisplayContent dc = getDisplayContent(); - return dc != null && dc.okToAnimate(ignoreFrozen); + return okToAnimate(false /* ignoreFrozen */, false /* ignoreScreenOn */); } boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) { @@ -2885,6 +2983,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Override public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { mLastLayer = -1; + mAnimationLeash = leash; reassignLayer(t); // Leash is now responsible for position, so set our position to 0. @@ -2894,11 +2993,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Override public void onAnimationLeashLost(Transaction t) { mLastLayer = -1; - mSurfaceFreezer.unfreeze(t); + mAnimationLeash = null; reassignLayer(t); updateSurfacePosition(t); } + @Override + public SurfaceControl getAnimationLeash() { + return mAnimationLeash; + } + private void doAnimationFinished(@AnimationType int type, AnimationAdapter anim) { for (int i = 0; i < mSurfaceAnimationSources.size(); ++i) { mSurfaceAnimationSources.valueAt(i).onAnimationFinished(type, anim); @@ -3049,7 +3153,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) void updateSurfacePosition(Transaction t) { - if (mSurfaceControl == null || mSurfaceAnimator.hasLeash()) { + if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) { return; } @@ -3124,6 +3228,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** Cheap way of doing cast and instanceof. */ + TaskFragment asTaskFragment() { + return null; + } + + /** Cheap way of doing cast and instanceof. */ WindowToken asWindowToken() { return null; } @@ -3166,6 +3275,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return false; } + /** @return {@code true} if this is a container for embedded activities or tasks. */ + boolean isEmbedded() { + return false; + } + /** * @return {@code true} if this container's surface should be shown when it is created. */ @@ -3331,6 +3445,20 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** + * Special helper to check that all windows are synced (vs just top one). This is only + * used to differentiate between starting-window vs full-drawn in activity-metrics reporting. + */ + boolean allSyncFinished() { + if (!isVisibleRequested()) return true; + if (mSyncState != SYNC_STATE_READY) return false; + for (int i = mChildren.size() - 1; i >= 0; --i) { + final WindowContainer child = mChildren.get(i); + if (!child.allSyncFinished()) return false; + } + return true; + } + + /** * Called during reparent to handle sync state when the hierarchy changes. * If this is in a sync group and gets reparented out, it will cancel syncing. * If this is not in a sync group and gets parented into one, it will prepare itself. @@ -3385,6 +3513,29 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** + * Forces the receiver container to always use the configuration of the supplier container as + * its requested override configuration. It allows to propagate configuration without changing + * the relationship between child and parent. + */ + static void overrideConfigurationPropagation(WindowContainer<?> receiver, + WindowContainer<?> supplier) { + final ConfigurationContainerListener listener = new ConfigurationContainerListener() { + @Override + public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) { + receiver.onRequestedOverrideConfigurationChanged(supplier.getConfiguration()); + } + }; + supplier.registerConfigurationChangeListener(listener); + receiver.registerWindowContainerListener(new WindowContainerListener() { + @Override + public void onRemoved() { + receiver.unregisterWindowContainerListener(this); + supplier.unregisterConfigurationChangeListener(listener); + } + }); + } + + /** * Returns the {@link WindowManager.LayoutParams.WindowType}. */ @WindowManager.LayoutParams.WindowType int getWindowType() { @@ -3398,4 +3549,73 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< getPendingTransaction().setSecure(mSurfaceControl, !canScreenshot); return true; } + + private class AnimationRunnerBuilder { + /** + * Runs when the surface stops animating + */ + private final List<Runnable> mOnAnimationFinished = new LinkedList<>(); + /** + * Runs when the animation is cancelled but the surface is still animating + */ + private final List<Runnable> mOnAnimationCancelled = new LinkedList<>(); + + private void setTaskBackgroundColor(@ColorInt int backgroundColor) { + TaskDisplayArea taskDisplayArea = getTaskDisplayArea(); + + if (taskDisplayArea != null) { + taskDisplayArea.setBackgroundColor(backgroundColor); + + // Atomic counter to make sure the clearColor callback is only called one. + // It will be called twice in the case we cancel the animation without restart + // (in that case it will run as the cancel and finished callbacks). + final AtomicInteger callbackCounter = new AtomicInteger(0); + final Runnable clearBackgroundColorHandler = () -> { + if (callbackCounter.getAndIncrement() == 0) { + taskDisplayArea.clearBackgroundColor(); + } + }; + + // We want to make sure this is called both when the surface stops animating and + // also when an animation is cancelled (i.e. animation is replaced by another + // animation but and so the surface is still animating) + mOnAnimationFinished.add(clearBackgroundColorHandler); + mOnAnimationCancelled.add(clearBackgroundColorHandler); + } + } + + private void hideInsetSourceViewOverflows(Set<Integer> insetTypes) { + final ArrayList<SurfaceControl> surfaceControls = + new ArrayList<>(insetTypes.size()); + + for (int insetType : insetTypes) { + InsetsSourceProvider insetProvider = getDisplayContent().getInsetsStateController() + .getSourceProvider(insetType); + + // Will apply it immediately to current leash and to all future inset animations + // until we disable it. + insetProvider.setCropToProvidingInsetsBounds(getPendingTransaction()); + + // Only clear the size restriction of the inset once the surface animation is over + // and not if it's canceled to be replace by another animation. + mOnAnimationFinished.add(() -> { + insetProvider.removeCropToProvidingInsetsBounds(getPendingTransaction()); + }); + } + } + + private IAnimationStarter build() { + return (Transaction t, AnimationAdapter adapter, boolean hidden, + @AnimationType int type, @Nullable AnimationAdapter snapshotAnim) -> { + startAnimation(getPendingTransaction(), adapter, !isVisible(), type, + (animType, anim) -> mOnAnimationFinished.forEach(Runnable::run), + () -> mOnAnimationCancelled.forEach(Runnable::run), snapshotAnim); + }; + } + } + + private interface IAnimationStarter { + void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, + @AnimationType int type, @Nullable AnimationAdapter snapshotAnim); + } } diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java index 86e356a876b5..7956a112539e 100644 --- a/services/core/java/com/android/server/wm/WindowContextListenerController.java +++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java @@ -17,7 +17,9 @@ package com.android.server.wm; import static android.view.Display.INVALID_DISPLAY; +import static android.view.Display.isSuspendedState; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; +import static android.window.WindowProviderService.isWindowProviderService; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR; @@ -80,7 +82,7 @@ class WindowContextListenerController { * @param options a bundle used to pass window-related options. */ void registerWindowContainerListener(@NonNull IBinder clientToken, - @NonNull WindowContainer container, int ownerUid, @WindowType int type, + @NonNull WindowContainer<?> container, int ownerUid, @WindowType int type, @Nullable Bundle options) { WindowContextListenerImpl listener = mListeners.get(clientToken); if (listener == null) { @@ -103,6 +105,16 @@ class WindowContextListenerController { listener.unregister(); } + void dispatchPendingConfigurationIfNeeded(int displayId) { + for (int i = mListeners.size() - 1; i >= 0; --i) { + final WindowContextListenerImpl listener = mListeners.valueAt(i); + if (listener.getWindowContainer().getDisplayContent().getDisplayId() == displayId + && listener.mHasPendingConfiguration) { + listener.reportConfigToWindowTokenClient(); + } + } + } + /** * Verifies if the caller is allowed to do the operation to the listener specified by * {@code clientToken}. @@ -138,7 +150,7 @@ class WindowContextListenerController { return listener != null ? listener.mOptions : null; } - @Nullable WindowContainer getContainer(IBinder clientToken) { + @Nullable WindowContainer<?> getContainer(IBinder clientToken) { final WindowContextListenerImpl listener = mListeners.get(clientToken); return listener != null ? listener.mContainer : null; } @@ -161,9 +173,9 @@ class WindowContextListenerController { @VisibleForTesting class WindowContextListenerImpl implements WindowContainerListener { - @NonNull private final IBinder mClientToken; + @NonNull private final IWindowToken mClientToken; private final int mOwnerUid; - @NonNull private WindowContainer mContainer; + @NonNull private WindowContainer<?> mContainer; /** * The options from {@link Context#createWindowContext(int, Bundle)}. * <p>It can be used for choosing the {@link DisplayArea} where the window context @@ -177,9 +189,11 @@ class WindowContextListenerController { private int mLastReportedDisplay = INVALID_DISPLAY; private Configuration mLastReportedConfig; - private WindowContextListenerImpl(IBinder clientToken, WindowContainer container, + private boolean mHasPendingConfiguration; + + private WindowContextListenerImpl(IBinder clientToken, WindowContainer<?> container, int ownerUid, @WindowType int type, @Nullable Bundle options) { - mClientToken = clientToken; + mClientToken = IWindowToken.Stub.asInterface(clientToken); mContainer = Objects.requireNonNull(container); mOwnerUid = ownerUid; mType = type; @@ -191,17 +205,17 @@ class WindowContextListenerController { mDeathRecipient = deathRecipient; } catch (RemoteException e) { ProtoLog.e(WM_ERROR, "Could not register window container listener token=%s, " - + "container=%s", mClientToken, mContainer); + + "container=%s", clientToken, mContainer); } } /** TEST ONLY: returns the {@link WindowContainer} of the listener */ @VisibleForTesting - WindowContainer getWindowContainer() { + WindowContainer<?> getWindowContainer() { return mContainer; } - private void updateContainer(@NonNull WindowContainer newContainer) { + private void updateContainer(@NonNull WindowContainer<?> newContainer) { Objects.requireNonNull(newContainer); if (mContainer.equals(newContainer)) { @@ -214,17 +228,17 @@ class WindowContextListenerController { } private void register() { + final IBinder token = mClientToken.asBinder(); if (mDeathRecipient == null) { - throw new IllegalStateException("Invalid client token: " + mClientToken); + throw new IllegalStateException("Invalid client token: " + token); } - mListeners.putIfAbsent(mClientToken, this); + mListeners.putIfAbsent(token, this); mContainer.registerWindowContainerListener(this); - reportConfigToWindowTokenClient(); } private void unregister() { mContainer.unregisterWindowContainerListener(this); - mListeners.remove(mClientToken); + mListeners.remove(mClientToken.asBinder()); } private void clear() { @@ -244,14 +258,27 @@ class WindowContextListenerController { private void reportConfigToWindowTokenClient() { if (mDeathRecipient == null) { - throw new IllegalStateException("Invalid client token: " + mClientToken); + throw new IllegalStateException("Invalid client token: " + mClientToken.asBinder()); } - + final DisplayContent dc = mContainer.getDisplayContent(); + if (!dc.isReady()) { + // Do not report configuration when booting. The latest configuration will be sent + // when WindowManagerService#displayReady(). + return; + } + // If the display of window context associated window container is suspended, don't + // report the configuration update. Note that we still dispatch the configuration update + // to WindowProviderService to make it compatible with Service#onConfigurationChanged. + // Service always receives #onConfigurationChanged callback regardless of display state. + if (!isWindowProviderService(mOptions) && isSuspendedState(dc.getDisplayInfo().state)) { + mHasPendingConfiguration = true; + return; + } + final Configuration config = mContainer.getConfiguration(); + final int displayId = dc.getDisplayId(); if (mLastReportedConfig == null) { mLastReportedConfig = new Configuration(); } - final Configuration config = mContainer.getConfiguration(); - final int displayId = mContainer.getDisplayContent().getDisplayId(); if (config.equals(mLastReportedConfig) && displayId == mLastReportedDisplay) { // No changes since last reported time. return; @@ -260,18 +287,18 @@ class WindowContextListenerController { mLastReportedConfig.setTo(config); mLastReportedDisplay = displayId; - IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(mClientToken); try { - windowTokenClient.onConfigurationChanged(config, displayId); + mClientToken.onConfigurationChanged(config, displayId); } catch (RemoteException e) { ProtoLog.w(WM_ERROR, "Could not report config changes to the window token client."); } + mHasPendingConfiguration = false; } @Override public void onRemoved() { if (mDeathRecipient == null) { - throw new IllegalStateException("Invalid client token: " + mClientToken); + throw new IllegalStateException("Invalid client token: " + mClientToken.asBinder()); } final WindowToken windowToken = mContainer.asWindowToken(); if (windowToken != null && windowToken.isFromClient()) { @@ -283,15 +310,14 @@ class WindowContextListenerController { // If we cannot obtain the DisplayContent, the DisplayContent may also be removed. // We should proceed the removal process. if (dc != null) { - final DisplayArea da = dc.findAreaForToken(windowToken); + final DisplayArea<?> da = dc.findAreaForToken(windowToken); updateContainer(da); return; } } mDeathRecipient.unlinkToDeath(); - IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(mClientToken); try { - windowTokenClient.onWindowTokenRemoved(); + mClientToken.onWindowTokenRemoved(); } catch (RemoteException e) { ProtoLog.w(WM_ERROR, "Could not report token removal to the window token client."); } @@ -300,7 +326,7 @@ class WindowContextListenerController { @Override public String toString() { - return "WindowContextListenerImpl{clientToken=" + mClientToken + ", " + return "WindowContextListenerImpl{clientToken=" + mClientToken.asBinder() + ", " + "container=" + mContainer + "}"; } @@ -314,11 +340,11 @@ class WindowContextListenerController { } void linkToDeath() throws RemoteException { - mClientToken.linkToDeath(this, 0); + mClientToken.asBinder().linkToDeath(this, 0); } void unlinkToDeath() { - mClientToken.unlinkToDeath(this, 0); + mClientToken.asBinder().unlinkToDeath(this, 0); } } } diff --git a/services/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java index ffd6d21c1026..baea85439582 100644 --- a/services/core/java/com/android/server/wm/WindowFrames.java +++ b/services/core/java/com/android/server/wm/WindowFrames.java @@ -89,6 +89,11 @@ public class WindowFrames { final Rect mCompatFrame = new Rect(); /** + * {@code true} if the window frame is a simulated frame and attached to a decor window. + */ + boolean mIsSimulatingDecorWindow = false; + + /** * Whether the parent frame would have been different if there was no display cutout. */ private boolean mParentFrameWasClippedByDisplayCutout; diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java index 08404411c02b..c95470071e2d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java +++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java @@ -43,7 +43,6 @@ public class WindowManagerDebugConfig { static final boolean DEBUG_CONFIGURATION = false; static final boolean DEBUG_STARTING_WINDOW_VERBOSE = false; static final boolean DEBUG_WALLPAPER = false; - static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER; static final boolean DEBUG_DRAG = true; static final boolean DEBUG_SCREENSHOT = false; static final boolean DEBUG_LAYOUT_REPEATS = false; diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 4fac05c349c5..4e51a17e03e0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -40,6 +40,7 @@ import com.android.server.input.InputManagerService; import com.android.server.policy.WindowManagerPolicy; import java.util.List; +import java.util.Set; /** * Window manager local system service interface. @@ -54,17 +55,18 @@ public abstract class WindowManagerInternal { */ public interface AccessibilityControllerInternal { /** - * Enable the accessibility trace logging. + * Start tracing for the given logging types. + * @param loggingTypeFlags flags of the logging types enabled. */ - void startTrace(); + void startTrace(long loggingTypeFlags); /** - * Disable the accessibility trace logging. + * Disable accessibility tracing for all logging types. */ void stopTrace(); /** - * Is trace enabled or not. + * Is tracing enabled for any logging type. */ boolean isAccessibilityTracingEnabled(); @@ -73,20 +75,23 @@ public abstract class WindowManagerInternal { * * @param where A string to identify this log entry, which can be used to filter/search * through the tracing file. + * @param loggingTypeFlags The flags for the logging types this log entry belongs to. * @param callingParams The parameters for the method to be logged. * @param a11yDump The proto byte array for a11y state when the entry is generated. * @param callingUid The calling uid. * @param stackTrace The stack trace, null if not needed. + * @param ignoreStackEntries The stack entries can be removed */ void logTrace( - String where, String callingParams, byte[] a11yDump, int callingUid, - StackTraceElement[] stackTrace); + String where, long loggingTypeFlags, String callingParams, byte[] a11yDump, + int callingUid, StackTraceElement[] stackTrace, Set<String> ignoreStackEntries); /** * Add an accessibility trace entry. * * @param where A string to identify this log entry, which can be used to filter/search * through the tracing file. + * @param loggingTypeFlags The flags for the logging types this log entry belongs to. * @param callingParams The parameters for the method to be logged. * @param a11yDump The proto byte array for a11y state when the entry is generated. * @param callingUid The calling uid. @@ -94,9 +99,11 @@ public abstract class WindowManagerInternal { * @param timeStamp The time when the method to be logged is called. * @param processId The calling process Id. * @param threadId The calling thread Id. + * @param ignoreStackEntries The stack entries can be removed */ - void logTrace(String where, String callingParams, byte[] a11yDump, int callingUid, - StackTraceElement[] callStack, long timeStamp, int processId, long threadId); + void logTrace(String where, long loggingTypeFlags, String callingParams, + byte[] a11yDump, int callingUid, StackTraceElement[] callStack, long timeStamp, + int processId, long threadId, Set<String> ignoreStackEntries); } /** @@ -115,6 +122,16 @@ public abstract class WindowManagerInternal { */ void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId, IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows); + + /** + * Called when the display is reparented and becomes an embedded + * display. The {@link WindowsForAccessibilityCallback} with the given embedded + * display will be replaced by the {@link WindowsForAccessibilityCallback} + * associated with its parent display at the same time. + * + * @param embeddedDisplayId The embedded display Id. + */ + void onDisplayReparented(int embeddedDisplayId); } /** @@ -143,11 +160,11 @@ public abstract class WindowManagerInternal { void onRectangleOnScreenRequested(int left, int top, int right, int bottom); /** - * Notifies that the rotation changed. + * Notifies that the display size is changed when rotation or the + * logical display is changed. * - * @param rotation The current rotation. */ - void onRotationChanged(int rotation); + void onDisplaySizeChanged(); /** * Notifies that the context of the user changed. For example, an application @@ -177,7 +194,7 @@ public abstract class WindowManagerInternal { /** * Called when a pending app transition gets cancelled. * - * @param keyguardGoingAway true if keyguard going away transition transition got cancelled. + * @param keyguardGoingAway true if keyguard going away transition got cancelled. */ public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {} @@ -190,6 +207,7 @@ public abstract class WindowManagerInternal { * Called when an app transition gets started * * @param keyguardGoingAway true if keyguard going away transition is started. + * @param keyguardOccluding true if keyguard (un)occlude transition is started. * @param duration the total duration of the transition * @param statusBarAnimationStartTime the desired start time for all visual animations in * the status bar caused by this app transition in uptime millis @@ -201,8 +219,9 @@ public abstract class WindowManagerInternal { * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER}, * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}. */ - public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration, - long statusBarAnimationStartTime, long statusBarAnimationDuration) { + public int onAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration, long statusBarAnimationStartTime, + long statusBarAnimationDuration) { return 0; } @@ -672,24 +691,43 @@ public abstract class WindowManagerInternal { public abstract String getWindowName(@NonNull IBinder binder); /** - * Return the window name of IME Insets control target. + * The callback after the request of show/hide input method is sent. * + * @param show Whether to show or hide input method. + * @param focusedToken The token of focused window. + * @param requestToken The token of window who requests the change. * @param displayId The ID of the display which input method is currently focused. - * @return The corresponding {@link WindowState#getName()} + * @return The information of the input method target. */ - public abstract @Nullable String getImeControlTargetNameForLogging(int displayId); + public abstract ImeTargetInfo onToggleImeRequested(boolean show, + @NonNull IBinder focusedToken, @NonNull IBinder requestToken, int displayId); - /** - * Return the current window name of the input method is on top of. - * - * Note that the concept of this window is only reparent the target window behind the input - * method window, it may different with the window which reported by - * {@code InputMethodManagerService#reportStartInput} which has input connection. - * - * @param displayId The ID of the display which input method is currently focused. - * @return The corresponding {@link WindowState#getName()} - */ - public abstract @Nullable String getImeTargetNameForLogging(int displayId); + /** The information of input method target when IME is requested to show or hide. */ + public static class ImeTargetInfo { + public final String focusedWindowName; + public final String requestWindowName; + + /** The window name of IME Insets control target. */ + public final String imeControlTargetName; + + /** + * The current window name of the input method is on top of. + * <p> + * Note that the concept of this window is only used to reparent the target window behind + * the input method window, it may be different from the window reported by + * {@link com.android.server.inputmethod.InputMethodManagerService#reportStartInput} which + * has input connection. + */ + public final String imeLayerTargetName; + + public ImeTargetInfo(String focusedWindowName, String requestWindowName, + String imeControlTargetName, String imeLayerTargetName) { + this.focusedWindowName = focusedWindowName; + this.requestWindowName = requestWindowName; + this.imeControlTargetName = imeControlTargetName; + this.imeLayerTargetName = imeLayerTargetName; + } + } /** * Moves the {@link WindowToken} {@code binder} to the display specified by {@code displayId}. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 45e833e84789..a38b31d07d05 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.Manifest.permission.INPUT_CONSUMER; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; +import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS; import static android.Manifest.permission.MANAGE_APP_TOKENS; import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS; @@ -47,6 +48,7 @@ import static android.provider.Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_P import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; +import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; @@ -56,6 +58,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; +import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; @@ -84,8 +87,10 @@ import static android.view.WindowManagerGlobal.ADD_OKAY; import static android.view.WindowManagerGlobal.RELAYOUT_RES_BLAST_SYNC; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; +import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN; +import static android.window.WindowProviderService.isWindowProviderService; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT; @@ -167,8 +172,10 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; -import android.hardware.configstore.V1_0.ISurfaceFlingerConfigs; import android.hardware.configstore.V1_0.OptionalBool; +import android.hardware.configstore.V1_1.DisplayOrientation; +import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs; +import android.hardware.configstore.V1_1.OptionalDisplayOrientation; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.hardware.input.InputManager; @@ -221,6 +228,7 @@ import android.util.TypedValue; import android.util.proto.ProtoOutputStream; import android.view.Choreographer; import android.view.Display; +import android.view.DisplayAddress; import android.view.DisplayInfo; import android.view.Gravity; import android.view.IAppTransitionAnimationSpecsFuture; @@ -249,6 +257,7 @@ import android.view.InputEvent; import android.view.InputWindowHandle; import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.KeyEvent; import android.view.MagnificationSpec; import android.view.MotionEvent; @@ -258,6 +267,7 @@ import android.view.ScrollCaptureResponse; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; +import android.view.TaskTransitionSpec; import android.view.View; import android.view.WindowContentFrameStats; import android.view.WindowInsets; @@ -339,26 +349,6 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean PROFILE_ORIENTATION = false; static WindowState mFocusingWindow; String mFocusingActivity; - /** How much to multiply the policy's type layer, to reserve room - * for multiple windows of the same type and Z-ordering adjustment - * with TYPE_LAYER_OFFSET. */ - static final int TYPE_LAYER_MULTIPLIER = 10000; - - /** Offset from TYPE_LAYER_MULTIPLIER for moving a group of windows above - * or below others in the same layer. */ - static final int TYPE_LAYER_OFFSET = 1000; - - /** How much to increment the layer for each window, to reserve room - * for effect surfaces between them. - */ - static final int WINDOW_LAYER_MULTIPLIER = 5; - - /** - * Animation thumbnail is as far as possible below the window above - * the thumbnail (or in other words as far as possible above the window - * below it). - */ - static final int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER - 1; /** The maximum length we will accept for a loaded animation duration: * this is 10 seconds. @@ -447,19 +437,19 @@ public class WindowManagerService extends IWindowManager.Stub "persist.wm.enable_remote_keyguard_animation"; private static final int sEnableRemoteKeyguardAnimation = - SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 0); + SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1); /** * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY */ - public static final boolean sEnableRemoteKeyguardGoingAwayAnimation = !sEnableShellTransitions - && sEnableRemoteKeyguardAnimation >= 1; + public static final boolean sEnableRemoteKeyguardGoingAwayAnimation = + sEnableRemoteKeyguardAnimation >= 1; /** * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY */ - public static final boolean sEnableRemoteKeyguardOccludeAnimation = !sEnableShellTransitions - && sEnableRemoteKeyguardAnimation >= 2; + public static final boolean sEnableRemoteKeyguardOccludeAnimation = + sEnableRemoteKeyguardAnimation >= 2; /** * Allows a fullscreen windowing mode activity to launch in its desired orientation directly @@ -467,6 +457,8 @@ public class WindowManagerService extends IWindowManager.Stub */ static final boolean ENABLE_FIXED_ROTATION_TRANSFORM = SystemProperties.getBoolean("persist.wm.fixed_rotation_transform", true); + private @Surface.Rotation int mPrimaryDisplayOrientation = Surface.ROTATION_0; + private DisplayAddress mPrimaryDisplayPhysicalAddress; // Enums for animation scale update types. @Retention(RetentionPolicy.SOURCE) @@ -650,7 +642,7 @@ public class WindowManagerService extends IWindowManager.Stub /** List of window currently causing non-system overlay windows to be hidden. */ private ArrayList<WindowState> mHidingNonSystemOverlayWindows = new ArrayList<>(); - AccessibilityController mAccessibilityController; + final AccessibilityController mAccessibilityController; private RecentsAnimationController mRecentsAnimationController; Watermark mWatermark; @@ -769,6 +761,11 @@ public class WindowManagerService extends IWindowManager.Stub */ final Handler mAnimationHandler = new Handler(AnimationThread.getHandler().getLooper()); + /** + * Used during task transitions to allow SysUI and launcher to customize task transitions. + */ + TaskTransitionSpec mTaskTransitionSpec; + boolean mHardKeyboardAvailable; WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener; SettingsObserver mSettingsObserver; @@ -1238,7 +1235,9 @@ public class WindowManagerService extends IWindowManager.Stub mAssistantOnTopOfDream = context.getResources().getBoolean( com.android.internal.R.bool.config_assistantOnTopOfDream); - mLetterboxConfiguration = new LetterboxConfiguration(context); + mLetterboxConfiguration = new LetterboxConfiguration( + // Using SysUI context to have access to Material colors extracted from Wallpaper. + ActivityThread.currentActivityThread().getSystemUiContext()); mInputManager = inputManager; // Must be before createDisplayContentLocked. mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); @@ -1394,6 +1393,7 @@ public class WindowManagerService extends IWindowManager.Stub mStartingSurfaceController = new StartingSurfaceController(this); mBlurController = new BlurController(mContext, mPowerManager); + mAccessibilityController = new AccessibilityController(this); } DisplayAreaPolicy.Provider getDisplayAreaPolicyProvider() { @@ -1459,7 +1459,7 @@ public class WindowManagerService extends IWindowManager.Stub } public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility, - int displayId, int requestUserId, InsetsState requestedVisibility, + int displayId, int requestUserId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { Arrays.fill(outActiveControls, null); @@ -1681,7 +1681,8 @@ public class WindowManagerService extends IWindowManager.Stub final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); displayPolicy.adjustWindowParamsLw(win, win.mAttrs); - win.updateRequestedVisibility(requestedVisibility); + win.setRequestedVisibilities(requestedVisibilities); + attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid); res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid); if (res != ADD_OKAY) { @@ -1732,16 +1733,22 @@ public class WindowManagerService extends IWindowManager.Stub && mWindowContextListenerController.hasListener(windowContextToken)) { final int windowContextType = mWindowContextListenerController .getWindowType(windowContextToken); + final Bundle options = mWindowContextListenerController + .getOptions(windowContextToken); if (type != windowContextType) { ProtoLog.w(WM_ERROR, "Window types in WindowContext and" + " LayoutParams.type should match! Type from LayoutParams is %d," + " but type from WindowContext is %d", type, windowContextType); - return WindowManagerGlobal.ADD_INVALID_TYPE; + // We allow WindowProviderService to add window other than windowContextType, + // but the WindowProviderService won't be associated with the window's + // WindowToken. + if (!isWindowProviderService(options)) { + return WindowManagerGlobal.ADD_INVALID_TYPE; + } + } else { + mWindowContextListenerController.registerWindowContainerListener( + windowContextToken, token, callingUid, type, options); } - final Bundle options = mWindowContextListenerController - .getOptions(windowContextToken); - mWindowContextListenerController.registerWindowContainerListener( - windowContextToken, token, callingUid, type, options); } // From now on, no exceptions or errors allowed! @@ -1771,18 +1778,16 @@ public class WindowManagerService extends IWindowManager.Stub final boolean hideSystemAlertWindows = !mHidingNonSystemOverlayWindows.isEmpty(); win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows); - final ActivityRecord tokenActivity = token.asActivityRecord(); - if (type == TYPE_APPLICATION_STARTING && tokenActivity != null) { - tokenActivity.mStartingWindow = win; - ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s", - activity, win); - } - boolean imMayMove = true; win.mToken.addWindow(win); displayPolicy.addWindowLw(win, attrs); - if (type == TYPE_INPUT_METHOD) { + displayPolicy.setDropInputModePolicy(win, win.mAttrs); + if (type == TYPE_APPLICATION_STARTING && activity != null) { + activity.attachStartingWindow(win); + ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s", + activity, win); + } else if (type == TYPE_INPUT_METHOD) { displayContent.setInputMethodWindowLocked(win); imMayMove = false; } else if (type == TYPE_INPUT_METHOD_DIALOG) { @@ -1808,7 +1813,8 @@ public class WindowManagerService extends IWindowManager.Stub winAnimator.mEnterAnimationPending = true; winAnimator.mEnteringAnimation = true; // Check if we need to prepare a transition for replacing window first. - if (activity != null && activity.isVisible() + if (!win.mTransitionController.isShellTransitionsEnabled() + && activity != null && activity.isVisible() && !prepareWindowReplacementTransition(activity)) { // If not, check if need to set up a dummy transition during display freeze // so that the unfreeze wait for the apps to draw. This might be needed if @@ -1856,7 +1862,7 @@ public class WindowManagerService extends IWindowManager.Stub ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s" + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5)); - if (win.isVisibleOrAdding() && displayContent.updateOrientation()) { + if (win.isVisibleRequestedOrAdding() && displayContent.updateOrientation()) { displayContent.sendNewConfiguration(); } @@ -2167,7 +2173,7 @@ public class WindowManagerService extends IWindowManager.Stub mWindowPlacerLocked.performSurfacePlacement(); // We need to report touchable region changes to accessibility. - if (mAccessibilityController != null) { + if (mAccessibilityController.hasCallbacks()) { mAccessibilityController.onSomeWindowResizedOrMovedWithCallingUid( uid, w.getDisplayContent().getDisplayId()); } @@ -2180,7 +2186,7 @@ public class WindowManagerService extends IWindowManager.Stub public void onRectangleOnScreenRequested(IBinder token, Rect rectangle) { synchronized (mGlobalLock) { - if (mAccessibilityController != null) { + if (mAccessibilityController.hasCallbacks()) { WindowState window = mWindowMap.get(token); if (window != null) { mAccessibilityController.onRectangleOnScreenRequested( @@ -2243,6 +2249,7 @@ public class WindowManagerService extends IWindowManager.Stub if (attrs != null) { displayPolicy.adjustWindowParamsLw(win, attrs); win.mToken.adjustWindowParams(win, attrs); + attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), uid, pid); int disableFlags = (attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility) & DISABLE_MASK; if (disableFlags != 0 && !hasStatusBarPermission(pid, uid)) { @@ -2270,7 +2277,7 @@ public class WindowManagerService extends IWindowManager.Stub win.mActivityRecord.checkKeyguardFlagsChanged(); } if (((attrChanges & LayoutParams.ACCESSIBILITY_TITLE_CHANGED) != 0) - && (mAccessibilityController != null)) { + && (mAccessibilityController.hasCallbacks())) { // No move or resize, but the controller checks for title changes as well mAccessibilityController.onSomeWindowResizedOrMovedWithCallingUid( uid, win.getDisplayContent().getDisplayId()); @@ -2465,16 +2472,22 @@ public class WindowManagerService extends IWindowManager.Stub configChanged = displayContent.updateOrientation(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - final DisplayInfo rotatedDisplayInfo = - win.mToken.getFixedRotationTransformDisplayInfo(); - if (rotatedDisplayInfo != null) { - outSurfaceControl.setTransformHint(rotatedDisplayInfo.rotation); - } else { - // We have to update the transform hint of display here, but we need to get if from - // SurfaceFlinger, so set it as rotation of display for most cases, then - // SurfaceFlinger would still update the transform hint of display in next frame. - outSurfaceControl.setTransformHint(displayContent.getDisplayInfo().rotation); - } + final DisplayInfo displayInfo = win.getDisplayInfo(); + int transformHint = displayInfo.rotation; + // If the window is on the primary display, use the panel orientation to adjust the + // transform hint + final boolean isPrimaryDisplay = displayInfo.address != null && + displayInfo.address.equals(mPrimaryDisplayPhysicalAddress); + if (isPrimaryDisplay) { + transformHint = (transformHint + mPrimaryDisplayOrientation) % 4; + } + outSurfaceControl.setTransformHint( + SurfaceControl.rotationToBufferTransform(transformHint)); + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Passing transform hint %d for window %s%s", + transformHint, win, + isPrimaryDisplay ? " on primary display with orientation " + + mPrimaryDisplayOrientation : ""); if (toBeDisplayed && win.mIsWallpaper) { displayContent.mWallpaperController.updateWallpaperOffset(win, false /* sync */); @@ -2482,7 +2495,7 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mActivityRecord != null) { win.mActivityRecord.updateReportedVisibilityLocked(); } - if (displayPolicy.areSystemBarsForcedShownLw(win)) { + if (displayPolicy.areSystemBarsForcedShownLw()) { result |= WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS; } if (!win.isGoneForLayout()) { @@ -2528,7 +2541,8 @@ public class WindowManagerService extends IWindowManager.Stub Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } if (winAnimator.mSurfaceController != null) { - win.calculateSurfaceBounds(win.getAttrs(), mTmpRect); + win.calculateSurfaceBounds(win.getLayoutingAttrs( + win.getWindowConfiguration().getRotation()), mTmpRect); outSurfaceSize.set(mTmpRect.width(), mTmpRect.height()); } getInsetsSourceControls(win, outActiveControls); @@ -2566,7 +2580,7 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mAttrs.type == TYPE_APPLICATION_STARTING) { transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE; } - if (mAtmService.getTransitionController().inTransition(win)) { + if (win.inTransition()) { focusMayChange = true; win.mAnimatingExit = true; } else if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) { @@ -2578,20 +2592,24 @@ public class WindowManagerService extends IWindowManager.Stub // an exit. win.mAnimatingExit = true; } else if (win.mDisplayContent.okToAnimate() - && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)) { - // If the wallpaper is currently behind this - // window, we need to change both of them inside - // of a transaction to avoid artifacts. + && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win) + && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) { + // If the wallpaper is currently behind this app window, we need to change both of them + // inside of a transaction to avoid artifacts. + // For NotificationShade, sysui is in charge of running window animation and it updates + // the client view visibility only after both NotificationShade and the wallpaper are + // hidden. So we don't need to care about exit animation, but can destroy its surface + // immediately. win.mAnimatingExit = true; } else { - boolean stopped = win.mActivityRecord != null ? win.mActivityRecord.mAppStopped : true; + boolean stopped = win.mActivityRecord == null || win.mActivityRecord.mAppStopped; // We set mDestroying=true so ActivityRecord#notifyAppStopped in-to destroy surfaces // will later actually destroy the surface if we do not do so here. Normally we leave // this to the exit animation. win.mDestroying = true; win.destroySurface(false, stopped); } - if (mAccessibilityController != null) { + if (mAccessibilityController.hasCallbacks()) { mAccessibilityController.onWindowTransition(win, transit); } @@ -3095,7 +3113,7 @@ public class WindowManagerService extends IWindowManager.Stub mSettingsObserver.updateSystemUiSettings(true /* handleChange */); synchronized (mGlobalLock) { // force a re-application of focused window sysui visibility on each display. - mRoot.forAllDisplayPolicies(DisplayPolicy::resetSystemUiVisibilityLw); + mRoot.forAllDisplayPolicies(DisplayPolicy::resetSystemBarAttributes); } } @@ -3840,6 +3858,14 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override + public SurfaceControl mirrorWallpaperSurface(int displayId) { + synchronized (mGlobalLock) { + final DisplayContent dc = mRoot.getDisplayContent(displayId); + return dc.mWallpaperController.mirrorWallpaperSurface(); + } + } + /** * Takes a snapshot of the screen. In landscape mode this grabs the whole screen. * In portrait mode, it grabs the upper region of the screen based on the vertical dimension @@ -4105,7 +4131,7 @@ public class WindowManagerService extends IWindowManager.Stub final boolean pendingRemoteRotation = rotationChanged && (displayContent.getDisplayRotation().isWaitingForRemoteRotation() - || mAtmService.getTransitionController().isCollecting()); + || displayContent.mTransitionController.isCollecting()); // Even if alwaysSend, we are waiting for a transition or remote to provide // rotated configuration, so we can't update configuration yet. if (!pendingRemoteRotation) { @@ -4225,7 +4251,7 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void modifyDisplayWindowInsets(int displayId, InsetsState state) { + public void updateDisplayWindowRequestedVisibilities(int displayId, InsetsVisibilities vis) { if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS); @@ -4237,7 +4263,7 @@ public class WindowManagerService extends IWindowManager.Stub if (dc == null || dc.mRemoteInsetsControlTarget == null) { return; } - dc.mRemoteInsetsControlTarget.updateRequestedVisibility(state); + dc.mRemoteInsetsControlTarget.setRequestedVisibilities(vis); dc.getInsetsStateController().onInsetsModified(dc.mRemoteInsetsControlTarget); } } finally { @@ -4936,6 +4962,9 @@ public class WindowManagerService extends IWindowManager.Stub mTaskSnapshotController.systemReady(); mHasWideColorGamutSupport = queryWideColorGamutSupport(); mHasHdrSupport = queryHdrSupport(); + mPrimaryDisplayOrientation = queryPrimaryDisplayOrientation(); + mPrimaryDisplayPhysicalAddress = + DisplayAddress.fromPhysicalDisplayId(SurfaceControl.getPrimaryPhysicalDisplayId()); UiThread.getHandler().post(mSettingsObserver::loadSettings); IVrManager vrManager = IVrManager.Stub.asInterface( ServiceManager.getService(Context.VR_SERVICE)); @@ -4955,6 +4984,9 @@ public class WindowManagerService extends IWindowManager.Stub } } + + // Keep logic in sync with SurfaceFlingerProperties.cpp + // Consider exposing properties via ISurfaceComposer instead. private static boolean queryWideColorGamutSupport() { boolean defaultValue = false; Optional<Boolean> hasWideColorProp = SurfaceFlingerProperties.has_wide_color_display(); @@ -4995,23 +5027,82 @@ public class WindowManagerService extends IWindowManager.Stub return false; } + private static @Surface.Rotation int queryPrimaryDisplayOrientation() { + Optional<SurfaceFlingerProperties.primary_display_orientation_values> prop = + SurfaceFlingerProperties.primary_display_orientation(); + if (prop.isPresent()) { + switch (prop.get()) { + case ORIENTATION_90: return Surface.ROTATION_90; + case ORIENTATION_180: return Surface.ROTATION_180; + case ORIENTATION_270: return Surface.ROTATION_270; + case ORIENTATION_0: + default: + return Surface.ROTATION_0; + } + } + try { + ISurfaceFlingerConfigs surfaceFlinger = ISurfaceFlingerConfigs.getService(); + OptionalDisplayOrientation primaryDisplayOrientation = + surfaceFlinger.primaryDisplayOrientation(); + if (primaryDisplayOrientation != null && primaryDisplayOrientation.specified) { + switch (primaryDisplayOrientation.value) { + case DisplayOrientation.ORIENTATION_90: return Surface.ROTATION_90; + case DisplayOrientation.ORIENTATION_180: return Surface.ROTATION_180; + case DisplayOrientation.ORIENTATION_270: return Surface.ROTATION_270; + case DisplayOrientation.ORIENTATION_0: + default: + return Surface.ROTATION_0; + } + } + } catch (Exception e) { + // Use default value if we can't talk to config store. + } + return Surface.ROTATION_0; + } + + // Returns an input target which is mapped to the given input token. This can be a WindowState + // or an embedded window. + @Nullable InputTarget getInputTargetFromToken(IBinder inputToken) { + WindowState windowState = mInputToWindowMap.get(inputToken); + if (windowState != null) { + return windowState; + } + + EmbeddedWindowController.EmbeddedWindow embeddedWindow = + mEmbeddedWindowController.get(inputToken); + if (embeddedWindow != null) { + return embeddedWindow; + } + + return null; + } + void reportFocusChanged(IBinder oldToken, IBinder newToken) { - WindowState lastFocus; - WindowState newFocus; + InputTarget lastTarget; + InputTarget newTarget; synchronized (mGlobalLock) { - lastFocus = mInputToWindowMap.get(oldToken); - newFocus = mInputToWindowMap.get(newToken); - ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus changing: %s -> %s", lastFocus, newFocus); + lastTarget = getInputTargetFromToken(oldToken); + newTarget = getInputTargetFromToken(newToken); + if (newTarget == null && lastTarget == null) { + Slog.v(TAG_WM, "Unknown focus tokens, dropping reportFocusChanged"); + return; + } + + mAccessibilityController.onFocusChanged(lastTarget, newTarget); + ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus changing: %s -> %s", lastTarget, newTarget); } - if (newFocus != null) { - mAnrController.onFocusChanged(newFocus); - newFocus.reportFocusChangedSerialized(true); + // Call WindowState focus change observers + WindowState newFocusedWindow = newTarget != null ? newTarget.getWindowState() : null; + if (newFocusedWindow != null && newFocusedWindow.mInputChannelToken == newToken) { + mAnrController.onFocusChanged(newFocusedWindow); + newFocusedWindow.reportFocusChangedSerialized(true); notifyFocusChanged(); } - if (lastFocus != null) { - lastFocus.reportFocusChangedSerialized(false); + WindowState lastFocusedWindow = lastTarget != null ? lastTarget.getWindowState() : null; + if (lastFocusedWindow != null && lastFocusedWindow.mInputChannelToken == oldToken) { + lastFocusedWindow.reportFocusChangedSerialized(false); } } @@ -5363,6 +5454,7 @@ public class WindowManagerService extends IWindowManager.Stub case LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED: { synchronized (mGlobalLock) { final DisplayContent displayContent = (DisplayContent) msg.obj; + displayContent.mLayoutAndAssignWindowLayersScheduled = false; displayContent.layoutAndAssignWindowLayersIfNeeded(); } break; @@ -5370,6 +5462,7 @@ public class WindowManagerService extends IWindowManager.Stub case WINDOW_STATE_BLAST_SYNC_TIMEOUT: { synchronized (mGlobalLock) { final WindowState ws = (WindowState) msg.obj; + Slog.i(TAG, "Blast sync timeout: " + ws); ws.immediatelyNotifyBlastSync(); } break; @@ -5467,6 +5560,25 @@ public class WindowManagerService extends IWindowManager.Stub } } + void setSandboxDisplayApis(int displayId, boolean sandboxDisplayApis) { + if (mContext.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Must hold permission " + WRITE_SECURE_SETTINGS); + } + + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + if (displayContent != null) { + displayContent.setSandboxDisplayApis(sandboxDisplayApis); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + /** The global settings only apply to default display. */ private boolean applyForcedPropertiesForDefaultDisplay() { boolean changed = false; @@ -5791,7 +5903,9 @@ public class WindowManagerService extends IWindowManager.Stub return; } - if (!displayContent.isReady() || !mPolicy.isScreenOn() || !displayContent.okToAnimate()) { + if (!displayContent.isReady() || !displayContent.getDisplayPolicy().isScreenOnFully() + || displayContent.getDisplayInfo().state == Display.STATE_OFF + || !displayContent.okToAnimate()) { // No need to freeze the screen before the display is ready, if the screen is off, // or we can't currently animate. return; @@ -6089,9 +6203,10 @@ public class WindowManagerService extends IWindowManager.Stub + " callers=" + Debug.getCallers(3)); return NAV_BAR_INVALID; } - displayContent.performLayout(false /* initial */, - false /* updateInputWindows */); - return displayContent.getDisplayPolicy().getNavBarPosition(); + return displayContent.getDisplayPolicy().navigationBarPosition( + displayContent.mBaseDisplayWidth, + displayContent.mBaseDisplayHeight, + displayContent.getDisplayRotation().getRotation()); } } @@ -6394,6 +6509,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mGlobalConfiguration="); pw.println(mRoot.getConfiguration()); pw.print(" mHasPermanentDpad="); pw.println(mHasPermanentDpad); mRoot.dumpTopFocusedDisplayId(pw); + mRoot.dumpDefaultMinSizeOfResizableTask(pw); mRoot.forAllDisplays(dc -> { final int displayId = dc.getDisplayId(); final InsetsControlTarget imeLayeringTarget = dc.getImeTarget(IME_TARGET_LAYERING); @@ -6427,7 +6543,7 @@ public class WindowManagerService extends IWindowManager.Stub mInputManagerCallback.dump(pw, " "); mTaskSnapshotController.dump(pw, " "); - if (mAccessibilityController != null) { + if (mAccessibilityController.hasCallbacks()) { mAccessibilityController.dump(pw, " "); } @@ -7447,7 +7563,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void setMagnificationSpec(int displayId, MagnificationSpec spec) { synchronized (mGlobalLock) { - if (mAccessibilityController != null) { + if (mAccessibilityController.hasCallbacks()) { mAccessibilityController.setMagnificationSpec(displayId, spec); } else { throw new IllegalStateException("Magnification callbacks not set!"); @@ -7458,7 +7574,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void setForceShowMagnifiableBounds(int displayId, boolean show) { synchronized (mGlobalLock) { - if (mAccessibilityController != null) { + if (mAccessibilityController.hasCallbacks()) { mAccessibilityController.setForceShowMagnifiableBounds(displayId, show); } else { throw new IllegalStateException("Magnification callbacks not set!"); @@ -7469,7 +7585,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion) { synchronized (mGlobalLock) { - if (mAccessibilityController != null) { + if (mAccessibilityController.hasCallbacks()) { mAccessibilityController.getMagnificationRegion(displayId, magnificationRegion); } else { throw new IllegalStateException("Magnification callbacks not set!"); @@ -7485,7 +7601,7 @@ public class WindowManagerService extends IWindowManager.Stub return null; } MagnificationSpec spec = null; - if (mAccessibilityController != null) { + if (mAccessibilityController.hasCallbacks()) { spec = mAccessibilityController.getMagnificationSpecForWindow(windowState); } if ((spec == null || spec.isNop()) && windowState.mGlobalScale == 1.0f) { @@ -7504,16 +7620,7 @@ public class WindowManagerService extends IWindowManager.Stub public boolean setMagnificationCallbacks(int displayId, @Nullable MagnificationCallbacks callbacks) { synchronized (mGlobalLock) { - if (mAccessibilityController == null) { - mAccessibilityController = new AccessibilityController( - WindowManagerService.this); - } - boolean result = mAccessibilityController.setMagnificationCallbacks( - displayId, callbacks); - if (!mAccessibilityController.hasCallbacks()) { - mAccessibilityController = null; - } - return result; + return mAccessibilityController.setMagnificationCallbacks(displayId, callbacks); } } @@ -7521,17 +7628,8 @@ public class WindowManagerService extends IWindowManager.Stub public boolean setWindowsForAccessibilityCallback(int displayId, WindowsForAccessibilityCallback callback) { synchronized (mGlobalLock) { - if (mAccessibilityController == null) { - mAccessibilityController = new AccessibilityController( - WindowManagerService.this); - } - final boolean result = - mAccessibilityController.setWindowsForAccessibilityCallback( - displayId, callback); - if (!mAccessibilityController.hasCallbacks()) { - mAccessibilityController = null; - } - return result; + return mAccessibilityController + .setWindowsForAccessibilityCallback(displayId, callback); } } @@ -7543,11 +7641,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public IBinder getFocusedWindowToken() { synchronized (mGlobalLock) { - WindowState windowState = getFocusedWindowLocked(); - if (windowState != null) { - return windowState.mClient.asBinder(); - } - return null; + return mAccessibilityController.getFocusedWindowToken(); } } @@ -7640,6 +7734,7 @@ public class WindowManagerService extends IWindowManager.Stub public void registerAppTransitionListener(AppTransitionListener listener) { synchronized (mGlobalLock) { getDefaultDisplayContentLocked().mAppTransition.registerListenerLocked(listener); + mAtmService.getTransitionController().registerLegacyListener(listener); } } @@ -7703,13 +7798,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void computeWindowsForAccessibility(int displayId) { - final AccessibilityController accessibilityController; - synchronized (mGlobalLock) { - accessibilityController = mAccessibilityController; - } - if (accessibilityController != null) { - accessibilityController.performComputeChangedWindowsNot(displayId, true); - } + mAccessibilityController.performComputeChangedWindowsNot(displayId, true); } @Override @@ -7783,7 +7872,7 @@ public class WindowManagerService extends IWindowManager.Stub final WindowState currentFocus = displayContent.mCurrentFocus; if (currentFocus != null && currentFocus.mSession.mUid == uid && currentFocus.mSession.mPid == pid) { - return true; + return currentFocus.canBeImeTarget(); } } return false; @@ -7942,30 +8031,37 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public String getImeControlTargetNameForLogging(int displayId) { + public ImeTargetInfo onToggleImeRequested(boolean show, IBinder focusedToken, + IBinder requestToken, int displayId) { + final String focusedWindowName; + final String requestWindowName; + final String imeControlTargetName; + final String imeLayerTargetName; synchronized (mGlobalLock) { + final WindowState focusedWin = mWindowMap.get(focusedToken); + focusedWindowName = focusedWin != null ? focusedWin.getName() : "null"; + final WindowState requestWin = mWindowMap.get(requestToken); + requestWindowName = requestWin != null ? requestWin.getName() : "null"; final DisplayContent dc = mRoot.getDisplayContent(displayId); - if (dc == null) { - return null; - } - final InsetsControlTarget target = dc.getImeTarget(IME_TARGET_CONTROL); - if (target == null) { - return null; - } - final WindowState win = target.getWindow(); - return win != null ? win.getName() : target.toString(); - } - } - - @Override - public String getImeTargetNameForLogging(int displayId) { - synchronized (mGlobalLock) { - final DisplayContent dc = mRoot.getDisplayContent(displayId); - if (dc == null || dc.getImeTarget(IME_TARGET_LAYERING) == null) { - return null; + if (dc != null) { + final InsetsControlTarget controlTarget = dc.getImeTarget(IME_TARGET_CONTROL); + if (controlTarget != null) { + final WindowState w = InsetsControlTarget.asWindowOrNull(controlTarget); + imeControlTargetName = w != null ? w.getName() : controlTarget.toString(); + } else { + imeControlTargetName = "null"; + } + final InsetsControlTarget target = dc.getImeTarget(IME_TARGET_LAYERING); + imeLayerTargetName = target != null ? target.getWindow().getName() : "null"; + if (show) { + dc.onShowImeRequested(); + } + } else { + imeControlTargetName = imeLayerTargetName = "no-display"; } - return dc.getImeTarget(IME_TARGET_LAYERING).getWindow().getName(); } + return new ImeTargetInfo(focusedWindowName, requestWindowName, imeControlTargetName, + imeLayerTargetName); } @Override @@ -8163,11 +8259,20 @@ public class WindowManagerService extends IWindowManager.Stub // This could prevent if there is no container animation, we still have to apply the // pending transaction and exit waiting. mAnimator.mNotifyWhenNoAnimation = true; + boolean animateStarting = false; while (timeoutRemaining > 0) { + // Waiting until all starting windows has finished animating. + animateStarting = !mAtmService.getTransitionController().isShellTransitionsEnabled() + && mRoot.forAllActivities(ActivityRecord::hasStartingWindow); boolean isAnimating = mAnimator.isAnimationScheduled() - || mRoot.isAnimating(TRANSITION | CHILDREN, ANIMATION_TYPE_ALL); + || mRoot.isAnimating(TRANSITION | CHILDREN, ANIMATION_TYPE_ALL) + || animateStarting; if (!isAnimating) { - break; + // isAnimating is a legacy transition query and will be removed, so also add + // a check for whether this is in a shell-transition when not using legacy. + if (!mAtmService.getTransitionController().inTransition()) { + break; + } } long startTime = System.currentTimeMillis(); try { @@ -8181,13 +8286,14 @@ public class WindowManagerService extends IWindowManager.Stub WindowContainer animatingContainer; animatingContainer = mRoot.getAnimatingContainer(TRANSITION | CHILDREN, ANIMATION_TYPE_ALL); - if (mAnimator.isAnimationScheduled() || animatingContainer != null) { + if (mAnimator.isAnimationScheduled() || animatingContainer != null || animateStarting) { Slog.w(TAG, "Timed out waiting for animations to complete," + " animatingContainer=" + animatingContainer + " animationType=" + SurfaceAnimator.animationTypeToString( animatingContainer != null ? animatingContainer.mSurfaceAnimator.getAnimationType() - : SurfaceAnimator.ANIMATION_TYPE_NONE)); + : SurfaceAnimator.ANIMATION_TYPE_NONE) + + " animateStarting=" + animateStarting); } } } @@ -8229,11 +8335,11 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent, true /* includingParents */); } - handleTaskFocusChange(touchedWindow.getTask()); + handleTaskFocusChange(touchedWindow.getTask(), touchedWindow.mActivityRecord); } @VisibleForTesting - void handleTaskFocusChange(Task task) { + void handleTaskFocusChange(Task task, ActivityRecord touchedActivity) { if (task == null) { return; } @@ -8252,7 +8358,24 @@ public class WindowManagerService extends IWindowManager.Stub } } - mAtmService.setFocusedTask(task.mTaskId); + mAtmService.setFocusedTask(task.mTaskId, touchedActivity); + } + + /** + * You need ALLOW_SLIPPERY_TOUCHES permission to be able to set FLAG_SLIPPERY. + */ + private int sanitizeFlagSlippery(int flags, String windowName, int callingUid, int callingPid) { + if ((flags & FLAG_SLIPPERY) == 0) { + return flags; + } + final int permissionResult = mContext.checkPermission( + android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES, callingPid, callingUid); + if (permissionResult != PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "Removing FLAG_SLIPPERY from '" + windowName + + "' because it doesn't have ALLOW_SLIPPERY_TOUCHES permission"); + return flags & ~FLAG_SLIPPERY; + } + return flags; } /** @@ -8276,11 +8399,11 @@ public class WindowManagerService extends IWindowManager.Stub clientChannel = win.openInputChannel(); mEmbeddedWindowController.add(clientChannel.getToken(), win); applicationHandle = win.getApplicationHandle(); - name = win.getName(); + name = win.toString(); } updateInputChannel(clientChannel.getToken(), callingUid, callingPid, displayId, surface, - name, applicationHandle, flags, privateFlags, type, null /* region */); + name, applicationHandle, flags, privateFlags, type, null /* region */, window); clientChannel.copyTo(outInputChannel); } @@ -8288,13 +8411,16 @@ public class WindowManagerService extends IWindowManager.Stub private void updateInputChannel(IBinder channelToken, int callingUid, int callingPid, int displayId, SurfaceControl surface, String name, InputApplicationHandle applicationHandle, int flags, - int privateFlags, int type, Region region) { + int privateFlags, int type, Region region, IWindow window) { InputWindowHandle h = new InputWindowHandle(applicationHandle, displayId); h.token = channelToken; + h.setWindowToken(window); h.name = name; + flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid); + final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE - | LayoutParams.FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE); + | FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE); h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | sanitizedFlags; h.layoutParamsType = type; h.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS; @@ -8341,12 +8467,12 @@ public class WindowManagerService extends IWindowManager.Stub Slog.e(TAG, "Couldn't find window for provided channelToken."); return; } - name = win.getName(); + name = win.toString(); applicationHandle = win.getApplicationHandle(); } updateInputChannel(channelToken, win.mOwnerUid, win.mOwnerPid, displayId, surface, name, - applicationHandle, flags, privateFlags, win.mWindowType, region); + applicationHandle, flags, privateFlags, win.mWindowType, region, win.mClient); } /** Return whether layer tracing is enabled */ @@ -8504,10 +8630,9 @@ public class WindowManagerService extends IWindowManager.Stub SurfaceControl.Transaction t = mTransactionFactory.get(); final int displayId = embeddedWindow.mDisplayId; if (grantFocus) { - t.setFocusedWindow(inputToken, embeddedWindow.getName(), displayId).apply(); + t.setFocusedWindow(inputToken, embeddedWindow.toString(), displayId).apply(); EventLog.writeEvent(LOGTAG_INPUT_FOCUS, - "Focus request " + embeddedWindow.getName(), - "reason=grantEmbeddedWindowFocus(true)"); + "Focus request " + embeddedWindow, "reason=grantEmbeddedWindowFocus(true)"); } else { // Search for a new focus target DisplayContent displayContent = mRoot.getDisplayContent(displayId); @@ -8516,18 +8641,18 @@ public class WindowManagerService extends IWindowManager.Stub if (newFocusTarget == null) { ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus remove request for " + "win=%s dropped since no candidate was found", - embeddedWindow.getName()); + embeddedWindow); return; } t.requestFocusTransfer(newFocusTarget.mInputChannelToken, newFocusTarget.getName(), - inputToken, embeddedWindow.getName(), + inputToken, embeddedWindow.toString(), displayId).apply(); EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Transfer focus request " + newFocusTarget, "reason=grantEmbeddedWindowFocus(false)"); } ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s", - embeddedWindow.getName(), grantFocus); + embeddedWindow, grantFocus); } } @@ -8556,24 +8681,24 @@ public class WindowManagerService extends IWindowManager.Stub } SurfaceControl.Transaction t = mTransactionFactory.get(); if (grantFocus) { - t.requestFocusTransfer(targetInputToken, embeddedWindow.getName(), + t.requestFocusTransfer(targetInputToken, embeddedWindow.toString(), hostWindow.mInputChannel.getToken(), hostWindow.getName(), hostWindow.getDisplayId()).apply(); EventLog.writeEvent(LOGTAG_INPUT_FOCUS, - "Transfer focus request " + embeddedWindow.getName(), + "Transfer focus request " + embeddedWindow, "reason=grantEmbeddedWindowFocus(true)"); } else { t.requestFocusTransfer(hostWindow.mInputChannel.getToken(), hostWindow.getName(), targetInputToken, - embeddedWindow.getName(), + embeddedWindow.toString(), hostWindow.getDisplayId()).apply(); EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Transfer focus request " + hostWindow, "reason=grantEmbeddedWindowFocus(false)"); } ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s", - embeddedWindow.getName(), grantFocus); + embeddedWindow, grantFocus); } } @@ -8625,7 +8750,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (win.mActivityRecord == null || !win.mActivityRecord.isState( - Task.ActivityState.RESUMED)) { + ActivityRecord.State.RESUMED)) { mDisplayHashController.sendDisplayHashError(callback, DISPLAY_HASH_ERROR_MISSING_WINDOW); return; @@ -8681,4 +8806,41 @@ public class WindowManagerService extends IWindowManager.Stub return snapshot != null && snapshot.hasImeSurface(); } } + + @Override + public int getImeDisplayId() { + // TODO(b/189805422): Add a toast to notify users that IMS may get extra + // onConfigurationChanged callback when perDisplayFocus is enabled. + // Enabling perDisplayFocus means that we track focus on each display, so we don't have + // the "top focus" display and getTopFocusedDisplayContent returns the default display + // as the fallback. It leads to InputMethodService receives an extra onConfiguration + // callback when InputMethodService move from a secondary display to another display + // with the same display metrics because InputMethodService will always associate with + // the ImeContainer on the default display in onCreate and receive a configuration update + // to match default display ImeContainer and then receive another configuration update + // from attachToWindowToken. + synchronized (mGlobalLock) { + final DisplayContent dc = mRoot.getTopFocusedDisplayContent(); + return dc.getImePolicy() == DISPLAY_IME_POLICY_LOCAL ? dc.getDisplayId() + : DEFAULT_DISPLAY; + } + } + + @Override + public void setTaskTransitionSpec(TaskTransitionSpec spec) { + if (!checkCallingPermission(MANAGE_ACTIVITY_TASKS, "setTaskTransitionSpec()")) { + throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission"); + } + + mTaskTransitionSpec = spec; + } + + @Override + public void clearTaskTransitionSpec() { + if (!checkCallingPermission(MANAGE_ACTIVITY_TASKS, "clearTaskTransitionSpec()")) { + throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission"); + } + + mTaskTransitionSpec = null; + } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index a82a478f2a4f..6970c7942a50 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -16,13 +16,23 @@ package com.android.server.wm; +import static android.Manifest.permission.START_TASKS_FROM_RECENTS; +import static android.app.ActivityManager.isStartResultSuccessful; +import static android.view.Display.DEFAULT_DISPLAY; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED; @@ -33,7 +43,12 @@ import static com.android.server.wm.WindowContainer.POSITION_TOP; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.IApplicationThread; import android.app.WindowConfiguration; +import android.content.ActivityNotFoundException; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; @@ -42,14 +57,21 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; +import android.util.AndroidRuntimeException; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import android.view.RemoteAnimationAdapter; import android.view.SurfaceControl; import android.window.IDisplayAreaOrganizerController; +import android.window.ITaskFragmentOrganizer; +import android.window.ITaskFragmentOrganizerController; import android.window.ITaskOrganizerController; +import android.window.ITransitionMetricsReporter; import android.window.ITransitionPlayer; import android.window.IWindowContainerTransactionCallback; import android.window.IWindowOrganizerController; +import android.window.TaskFragmentCreationParams; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; @@ -63,7 +85,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.function.Consumer; +import java.util.function.Function; /** * Server side implementation for the interface for organizing windows @@ -95,15 +117,27 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final TaskOrganizerController mTaskOrganizerController; final DisplayAreaOrganizerController mDisplayAreaOrganizerController; + final TaskFragmentOrganizerController mTaskFragmentOrganizerController; - final TransitionController mTransitionController; + TransitionController mTransitionController; + /** + * A Map which manages the relationship between + * {@link TaskFragmentCreationParams#getFragmentToken()} and {@link TaskFragment} + */ + @VisibleForTesting + final ArrayMap<IBinder, TaskFragment> mLaunchTaskFragments = new ArrayMap<>(); WindowOrganizerController(ActivityTaskManagerService atm) { mService = atm; mGlobalLock = atm.mGlobalLock; mTaskOrganizerController = new TaskOrganizerController(mService); mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService); - mTransitionController = new TransitionController(atm); + mTaskFragmentOrganizerController = new TaskFragmentOrganizerController(atm); + } + + void setWindowManager(WindowManagerService wms) { + mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController); + mTransitionController.registerLegacyListener(wms.mActivityManagerAppTransitionNotifier); } TransitionController getTransitionController() { @@ -122,14 +156,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub @Override public void applyTransaction(WindowContainerTransaction t) { - enforceTaskPermission("applyTransaction()"); if (t == null) { - throw new IllegalArgumentException("Null transaction passed to applySyncTransaction"); + throw new IllegalArgumentException("Null transaction passed to applyTransaction"); } + enforceTaskPermission("applyTransaction()", t); + final CallerInfo caller = new CallerInfo(); final long ident = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - applyTransaction(t, -1 /*syncId*/, null /*transition*/); + applyTransaction(t, -1 /*syncId*/, null /*transition*/, caller); } } finally { Binder.restoreCallingIdentity(ident); @@ -139,10 +174,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub @Override public int applySyncTransaction(WindowContainerTransaction t, IWindowContainerTransactionCallback callback) { - enforceTaskPermission("applySyncTransaction()"); if (t == null) { throw new IllegalArgumentException("Null transaction passed to applySyncTransaction"); } + enforceTaskPermission("applySyncTransaction()", t); + final CallerInfo caller = new CallerInfo(); final long ident = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { @@ -162,7 +198,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (callback != null) { syncId = startSyncWithOrganizer(callback); } - applyTransaction(t, syncId, null /*transition*/); + applyTransaction(t, syncId, null /*transition*/, caller); if (syncId >= 0) { setSyncReady(syncId); } @@ -177,6 +213,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub public IBinder startTransition(int type, @Nullable IBinder transitionToken, @Nullable WindowContainerTransaction t) { enforceTaskPermission("startTransition()"); + final CallerInfo caller = new CallerInfo(); final long ident = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { @@ -196,7 +233,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub throw new IllegalArgumentException("Can't use legacy transitions in" + " compatibility mode with no WCT."); } - applyTransaction(t, -1 /* syncId */, null); + applyTransaction(t, -1 /* syncId */, null, caller); return null; } transition = mTransitionController.createTransition(type); @@ -205,9 +242,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (t == null) { t = new WindowContainerTransaction(); } - applyTransaction(t, -1 /*syncId*/, transition); + applyTransaction(t, -1 /*syncId*/, transition, caller); if (needsSetReady) { - transition.setReady(); + transition.setAllReady(); } return transition; } @@ -217,10 +254,47 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } @Override + public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter, + @NonNull IWindowContainerTransactionCallback callback, + @NonNull WindowContainerTransaction t) { + enforceTaskPermission("startLegacyTransition()"); + final CallerInfo caller = new CallerInfo(); + final long ident = Binder.clearCallingIdentity(); + int syncId; + try { + synchronized (mGlobalLock) { + if (type < 0) { + throw new IllegalArgumentException("Can't create transition with no type"); + } + if (mTransitionController.getTransitionPlayer() != null) { + throw new IllegalArgumentException("Can't use legacy transitions in" + + " when shell transitions are enabled."); + } + final DisplayContent dc = + mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY); + if (dc.mAppTransition.isTransitionSet()) { + // a transition already exists, so the callback probably won't be called. + return -1; + } + adapter.setCallingPidUid(caller.mPid, caller.mUid); + dc.prepareAppTransition(type); + dc.mAppTransition.overridePendingAppTransitionRemote(adapter, true /* sync */); + syncId = startSyncWithOrganizer(callback); + applyTransaction(t, syncId, null /* transition */, caller); + setSyncReady(syncId); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + return syncId; + } + + @Override public int finishTransition(@NonNull IBinder transitionToken, @Nullable WindowContainerTransaction t, @Nullable IWindowContainerTransactionCallback callback) { enforceTaskPermission("finishTransition()"); + final CallerInfo caller = new CallerInfo(); final long ident = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { @@ -231,7 +305,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // apply the incoming transaction before finish in case it alters the visibility // of the participants. if (t != null) { - applyTransaction(t, syncId, null /*transition*/); + applyTransaction(t, syncId, null /*transition*/, caller); } getTransitionController().finishTransition(transitionToken); if (syncId >= 0) { @@ -247,17 +321,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub /** * @param syncId If non-null, this will be a sync-transaction. * @param transition A transition to collect changes into. + * @param caller Info about the calling process. */ private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId, - @Nullable Transition transition) { + @Nullable Transition transition, @NonNull CallerInfo caller) { int effects = 0; ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId); mService.deferWindowLayout(); + mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */); try { if (transition != null) { // First check if we have a display rotation transition and if so, update it. final DisplayContent dc = DisplayRotation.getDisplayFromTransition(transition); - if (dc != null && transition.mChanges.get(dc).mRotation != dc.getRotation()) { + if (dc != null && transition.mChanges.get(dc).hasChanged(dc)) { // Go through all tasks and collect them before the rotation // TODO(shell-transitions): move collect() to onConfigurationChange once // wallpaper handling is synchronized. @@ -303,7 +379,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final boolean isInLockTaskMode = mService.isInLockTaskMode(); for (int i = 0; i < hopSize; ++i) { effects |= applyHierarchyOp(hops.get(i), effects, syncId, transition, - isInLockTaskMode); + isInLockTaskMode, caller, t.getErrorCallbackToken(), + t.getTaskFragmentOrganizer()); } } // Queue-up bounds-change transactions for tasks which are now organized. Do @@ -341,6 +418,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub task.setMainWindowSizeChangeTransaction(sft); } if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) { + mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */); // Already calls ensureActivityConfig mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); mService.mRootWindowContainer.resumeFocusedTasksTopActivities(); @@ -362,6 +440,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.addWindowLayoutReasons(LAYOUT_REASON_CONFIG_CHANGED); } } finally { + mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */); mService.continueWindowLayout(); } } @@ -389,6 +468,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub container.onRequestedOverrideConfigurationChanged(c); } effects |= TRANSACT_EFFECTS_CLIENT_CONFIG; + if (windowMask != 0 && container.isEmbedded()) { + // Changing bounds of the embedded TaskFragments may result in lifecycle changes. + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } } if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) { if (container.setFocusable(change.getFocusable())) { @@ -402,7 +485,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub throw new UnsupportedOperationException("Not supported to set multi-window" + " windowing mode during locked task mode."); } + + final int prevMode = container.getWindowingMode(); container.setWindowingMode(windowingMode); + if (prevMode != container.getWindowingMode()) { + // The activity in the container may become focusable or non-focusable due to + // windowing modes changes (such as entering or leaving pinned windowing mode), + // so also apply the lifecycle effects to this transaction. + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } } return effects; } @@ -458,7 +549,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects, - int syncId, @Nullable Transition transition, boolean isInLockTaskMode) { + int syncId, @Nullable Transition transition, boolean isInLockTaskMode, + @NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken, + @Nullable ITaskFragmentOrganizer organizer) { final int type = hop.getType(); switch (type) { case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: { @@ -475,38 +568,143 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: { final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); final Task task = wc != null ? wc.asTask() : null; + final boolean clearRoot = hop.getToTop(); if (task == null) { throw new IllegalArgumentException("Cannot set non-task as launch root: " + wc); } else if (!task.mCreatedByOrganizer) { throw new UnsupportedOperationException( "Cannot set non-organized task as adjacent flag root: " + wc); - } else if (task.mAdjacentTask == null) { + } else if (task.getAdjacentTaskFragment() == null && !clearRoot) { throw new UnsupportedOperationException( "Cannot set non-adjacent task as adjacent flag root: " + wc); } - final boolean clearRoot = hop.getToTop(); task.getDisplayArea().setLaunchAdjacentFlagRootTask(clearRoot ? null : task); break; } - case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: + case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: { effects |= setAdjacentRootsHierarchyOp(hop); break; - } - // The following operations may change task order so they are skipped while in lock task - // mode. The above operations are still allowed because they don't move tasks. And it may - // be necessary such as clearing launch root after entering lock task mode. - if (isInLockTaskMode) { - Slog.w(TAG, "Skip applying hierarchy operation " + hop + " while in lock task mode"); - return effects; + } + case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: { + final TaskFragmentCreationParams taskFragmentCreationOptions = + hop.getTaskFragmentCreationOptions(); + createTaskFragment(taskFragmentCreationOptions, errorCallbackToken, caller); + break; + } + case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT: { + final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); + if (wc == null || !wc.isAttached()) { + Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc); + break; + } + final TaskFragment taskFragment = wc.asTaskFragment(); + if (taskFragment == null || taskFragment.asTask() != null) { + throw new IllegalArgumentException( + "Can only delete organized TaskFragment, but not Task."); + } + if (isInLockTaskMode) { + final ActivityRecord bottomActivity = taskFragment.getActivity( + a -> !a.finishing, false /* traverseTopToBottom */); + if (bottomActivity != null + && mService.getLockTaskController().activityBlockedFromFinish( + bottomActivity)) { + Slog.w(TAG, "Skip removing TaskFragment due in lock task mode."); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, + new IllegalStateException( + "Not allow to delete task fragment in lock task mode.")); + break; + } + } + effects |= deleteTaskFragment(taskFragment, errorCallbackToken); + break; + } + case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: { + final IBinder fragmentToken = hop.getContainer(); + if (!mLaunchTaskFragments.containsKey(fragmentToken)) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to operate with invalid fragment token"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + break; + } + final Intent activityIntent = hop.getActivityIntent(); + final Bundle activityOptions = hop.getLaunchOptions(); + final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken); + final int result = mService.getActivityStartController() + .startActivityInTaskFragment(tf, activityIntent, activityOptions, + hop.getCallingActivity(), caller.mUid, caller.mPid); + if (!isStartResultSuccessful(result)) { + sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(), + errorCallbackToken, + convertStartFailureToThrowable(result, activityIntent)); + } + break; + } + case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: { + final IBinder fragmentToken = hop.getNewParent(); + final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer()); + if (!mLaunchTaskFragments.containsKey(fragmentToken) || activity == null) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to operate with invalid fragment token or activity."); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + break; + } + activity.reparent(mLaunchTaskFragments.get(fragmentToken), POSITION_TOP); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + break; + } + case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS: { + final IBinder fragmentToken = hop.getContainer(); + final IBinder adjacentFragmentToken = hop.getAdjacentRoot(); + final TaskFragment tf1 = mLaunchTaskFragments.get(fragmentToken); + final TaskFragment tf2 = adjacentFragmentToken != null + ? mLaunchTaskFragments.get(adjacentFragmentToken) + : null; + if (tf1 == null || (adjacentFragmentToken != null && tf2 == null)) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to set adjacent on invalid fragment tokens"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + break; + } + tf1.setAdjacentTaskFragment(tf2, false /* moveAdjacentTogether */); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + + final Bundle bundle = hop.getLaunchOptions(); + final WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = + bundle != null ? new WindowContainerTransaction.TaskFragmentAdjacentParams( + bundle) : null; + if (adjacentParams == null) { + break; + } + + tf1.setDelayLastActivityRemoval( + adjacentParams.shouldDelayPrimaryLastActivityRemoval()); + if (tf2 != null) { + tf2.setDelayLastActivityRemoval( + adjacentParams.shouldDelaySecondaryLastActivityRemoval()); + } + break; + } + default: { + // The other operations may change task order so they are skipped while in lock + // task mode. The above operations are still allowed because they don't move + // tasks. And it may be necessary such as clearing launch root after entering + // lock task mode. + if (isInLockTaskMode) { + Slog.w(TAG, "Skip applying hierarchy operation " + hop + + " while in lock task mode"); + return effects; + } + } } switch (type) { - case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: + case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: { effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId); break; + } case HIERARCHY_OP_TYPE_REORDER: - case HIERARCHY_OP_TYPE_REPARENT: + case HIERARCHY_OP_TYPE_REPARENT: { final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); if (wc == null || !wc.isAttached()) { Slog.e(TAG, "Attempt to operate on detached container: " + wc); @@ -536,13 +734,76 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } effects |= sanitizeAndApplyHierarchyOp(wc, hop); break; - case HIERARCHY_OP_TYPE_LAUNCH_TASK: + } + case HIERARCHY_OP_TYPE_LAUNCH_TASK: { + mService.mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS, + "launchTask HierarchyOp"); final Bundle launchOpts = hop.getLaunchOptions(); final int taskId = launchOpts.getInt( WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID); launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID); - mService.startActivityFromRecents(taskId, launchOpts); + final SafeActivityOptions safeOptions = + SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid); + final Integer[] starterResult = {null}; + // startActivityFromRecents should not be called in lock. + mService.mH.post(() -> { + try { + starterResult[0] = mService.mTaskSupervisor.startActivityFromRecents( + caller.mPid, caller.mUid, taskId, safeOptions); + } catch (Throwable t) { + starterResult[0] = ActivityManager.START_CANCELED; + Slog.w(TAG, t); + } + synchronized (mGlobalLock) { + mGlobalLock.notifyAll(); + } + }); + while (starterResult[0] == null) { + try { + mGlobalLock.wait(); + } catch (InterruptedException ignored) { + } + } + break; + } + case HIERARCHY_OP_TYPE_PENDING_INTENT: { + String resolvedType = hop.getActivityIntent() != null + ? hop.getActivityIntent().resolveTypeIfNeeded( + mService.mContext.getContentResolver()) + : null; + + Bundle options = null; + if (hop.getPendingIntent().isActivity()) { + // Set the context display id as preferred for this activity launches, so that + // it can land on caller's display. Or just brought the task to front at the + // display where it was on since it has higher preference. + ActivityOptions activityOptions = hop.getLaunchOptions() != null + ? new ActivityOptions(hop.getLaunchOptions()) + : ActivityOptions.makeBasic(); + activityOptions.setCallerDisplayId(DEFAULT_DISPLAY); + options = activityOptions.toBundle(); + } + + mService.mAmInternal.sendIntentSender(hop.getPendingIntent().getTarget(), + hop.getPendingIntent().getWhitelistToken(), 0 /* code */, + hop.getActivityIntent(), resolvedType, null /* finishReceiver */, + null /* requiredPermission */, options); break; + } + case HIERARCHY_OP_TYPE_REPARENT_CHILDREN: { + final WindowContainer oldParent = WindowContainer.fromBinder(hop.getContainer()); + final WindowContainer newParent = hop.getNewParent() != null + ? WindowContainer.fromBinder(hop.getNewParent()) + : null; + if (oldParent == null || !oldParent.isAttached()) { + Slog.e(TAG, "Attempt to operate on unknown or detached container: " + + oldParent); + break; + } + reparentTaskFragment(oldParent, newParent, errorCallbackToken); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + break; + } } return effects; } @@ -661,24 +922,31 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // We want to collect the tasks first before re-parenting to avoid array shifting on us. final ArrayList<Task> tasksToReparent = new ArrayList<>(); - currentParent.forAllTasks((Consumer<Task>) (task) -> { + currentParent.forAllTasks((Function<Task, Boolean>) task -> { Slog.i(TAG, " Processing task=" + task); - if (task.mCreatedByOrganizer - || task.getParent() != finalCurrentParent) { + final boolean reparent; + if (task.mCreatedByOrganizer || task.getParent() != finalCurrentParent) { // We only care about non-organized task that are direct children of the thing we // are reparenting from. - return; + return false; } if (newParentInMultiWindow && !task.supportsMultiWindowInDisplayArea(newParentTda)) { Slog.e(TAG, "reparentChildrenTasksHierarchyOp non-resizeable task to multi window," + " task=" + task); - return; + return false; + } + if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType()) + || !ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) { + return false; } - if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())) return; - if (!ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) return; - tasksToReparent.add(task); - }, !hop.getToTop()); + if (hop.getToTop()) { + tasksToReparent.add(0, task); + } else { + tasksToReparent.add(task); + } + return hop.getReparentTopOnly() && tasksToReparent.size() == 1; + }); final int count = tasksToReparent.size(); for (int i = 0; i < count; ++i) { @@ -704,19 +972,20 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } private int setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) { - final Task root1 = WindowContainer.fromBinder(hop.getContainer()).asTask(); - final Task root2 = WindowContainer.fromBinder(hop.getAdjacentRoot()).asTask(); + final TaskFragment root1 = WindowContainer.fromBinder(hop.getContainer()).asTaskFragment(); + final TaskFragment root2 = + WindowContainer.fromBinder(hop.getAdjacentRoot()).asTaskFragment(); if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) { throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by" + " organizer root1=" + root1 + " root2=" + root2); } - root1.setAdjacentTask(root2); + root1.setAdjacentTaskFragment(root2, hop.getMoveAdjacentTogether()); return TRANSACT_EFFECTS_LIFECYCLE; } private void sanitizeWindowContainer(WindowContainer wc) { - if (!(wc instanceof Task) && !(wc instanceof DisplayArea)) { - throw new RuntimeException("Invalid token in task or displayArea transaction"); + if (!(wc instanceof TaskFragment) && !(wc instanceof DisplayArea)) { + throw new RuntimeException("Invalid token in task fragment or displayArea transaction"); } } @@ -747,6 +1016,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return mDisplayAreaOrganizerController; } + @Override + public ITaskFragmentOrganizerController getTaskFragmentOrganizerController() { + return mTaskFragmentOrganizerController; + } + @VisibleForTesting int startSyncWithOrganizer(IWindowContainerTransactionCallback callback) { int id = mService.mWindowManager.mSyncEngine.startSyncSet(this); @@ -785,17 +1059,259 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub @Override public void registerTransitionPlayer(ITransitionPlayer player) { enforceTaskPermission("registerTransitionPlayer()"); + final int callerPid = Binder.getCallingPid(); + final int callerUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - mTransitionController.registerTransitionPlayer(player); + final WindowProcessController wpc = + mService.getProcessController(callerPid, callerUid); + IApplicationThread appThread = null; + if (wpc != null) { + appThread = wpc.getThread(); + } + mTransitionController.registerTransitionPlayer(player, appThread); } } finally { Binder.restoreCallingIdentity(ident); } } + @Override + public ITransitionMetricsReporter getTransitionMetricsReporter() { + return mTransitionController.mTransitionMetricsReporter; + } + + /** Whether the configuration changes are important to report back to an organizer. */ + static boolean configurationsAreEqualForOrganizer( + Configuration newConfig, @Nullable Configuration oldConfig) { + if (oldConfig == null) { + return false; + } + int cfgChanges = newConfig.diff(oldConfig); + final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0 + ? (int) newConfig.windowConfiguration.diff(oldConfig.windowConfiguration, + true /* compareUndefined */) : 0; + if ((winCfgChanges & CONTROLLABLE_WINDOW_CONFIGS) == 0) { + cfgChanges &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION; + } + return (cfgChanges & CONTROLLABLE_CONFIGS) == 0; + } + private void enforceTaskPermission(String func) { mService.enforceTaskPermission(func); } + + private void enforceTaskPermission(String func, WindowContainerTransaction t) { + if (t == null || t.getTaskFragmentOrganizer() == null) { + enforceTaskPermission(func); + return; + } + + // Apps may not have the permission to manage Tasks, but we are allowing apps to manage + // TaskFragments belonging to their own Task. + enforceOperationsAllowedForTaskFragmentOrganizer(func, t); + } + + /** + * Makes sure that the transaction only contains operations that are allowed for the + * {@link WindowContainerTransaction#getTaskFragmentOrganizer()}. + */ + private void enforceOperationsAllowedForTaskFragmentOrganizer( + String func, WindowContainerTransaction t) { + final ITaskFragmentOrganizer organizer = t.getTaskFragmentOrganizer(); + + // Configuration changes + final Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries = + t.getChanges().entrySet().iterator(); + while (entries.hasNext()) { + final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next(); + // Only allow to apply changes to TaskFragment that is created by this organizer. + enforceTaskFragmentOrganized(func, WindowContainer.fromBinder(entry.getKey()), + organizer); + } + + // Hierarchy changes + final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps(); + for (int i = hops.size() - 1; i >= 0; i--) { + final WindowContainerTransaction.HierarchyOp hop = hops.get(i); + final int type = hop.getType(); + // Check for each type of the operations that are allowed for TaskFragmentOrganizer. + switch (type) { + case HIERARCHY_OP_TYPE_REORDER: + case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT: + enforceTaskFragmentOrganized(func, + WindowContainer.fromBinder(hop.getContainer()), organizer); + break; + case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: + enforceTaskFragmentOrganized(func, + WindowContainer.fromBinder(hop.getContainer()), organizer); + enforceTaskFragmentOrganized(func, + WindowContainer.fromBinder(hop.getAdjacentRoot()), + organizer); + break; + case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: + // We are allowing organizer to create TaskFragment. We will check the + // ownerToken in #createTaskFragment, and trigger error callback if that is not + // valid. + case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: + case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: + case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS: + // We are allowing organizer to start/reparent activity to a TaskFragment it + // created, or set two TaskFragments adjacent to each other. Nothing to check + // here because the TaskFragment may not be created yet, but will be created in + // the same transaction. + break; + case HIERARCHY_OP_TYPE_REPARENT_CHILDREN: + enforceTaskFragmentOrganized(func, + WindowContainer.fromBinder(hop.getContainer()), organizer); + if (hop.getNewParent() != null) { + enforceTaskFragmentOrganized(func, + WindowContainer.fromBinder(hop.getNewParent()), + organizer); + } + break; + default: + // Other types of hierarchy changes are not allowed. + String msg = "Permission Denial: " + func + " from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " trying to apply a hierarchy change that is not allowed for" + + " TaskFragmentOrganizer=" + organizer; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + } + } + + private void enforceTaskFragmentOrganized(String func, @Nullable WindowContainer wc, + ITaskFragmentOrganizer organizer) { + if (wc == null) { + Slog.e(TAG, "Attempt to operate on window that no longer exists"); + return; + } + + final TaskFragment tf = wc.asTaskFragment(); + if (tf == null || !tf.hasTaskFragmentOrganizer(organizer)) { + String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + " trying to modify window container not" + + " belonging to the TaskFragmentOrganizer=" + organizer; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + } + + void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams, + @Nullable IBinder errorCallbackToken, @NonNull CallerInfo caller) { + final ActivityRecord ownerActivity = + ActivityRecord.forTokenLocked(creationParams.getOwnerToken()); + final ITaskFragmentOrganizer organizer = ITaskFragmentOrganizer.Stub.asInterface( + creationParams.getOrganizer().asBinder()); + + if (ownerActivity == null || ownerActivity.getTask() == null) { + final Throwable exception = + new IllegalArgumentException("Not allowed to operate with invalid ownerToken"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + return; + } + if (!ownerActivity.isResizeable()) { + final IllegalArgumentException exception = new IllegalArgumentException("Not allowed" + + " to operate with non-resizable owner Activity"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + return; + } + // The ownerActivity has to belong to the same app as the target Task. + if (ownerActivity.getTask().effectiveUid != ownerActivity.getUid() + || ownerActivity.getTask().effectiveUid != caller.mUid) { + final Throwable exception = + new IllegalArgumentException("Not allowed to operate with the ownerToken while " + + "the root activity of the target task belong to the different app"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + return; + } + final TaskFragment taskFragment = new TaskFragment(mService, + creationParams.getFragmentToken(), true /* createdByOrganizer */); + // Set task fragment organizer immediately, since it might have to be notified about further + // actions. + taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(), + ownerActivity.getUid(), ownerActivity.info.processName); + ownerActivity.getTask().addChild(taskFragment, POSITION_TOP); + taskFragment.setWindowingMode(creationParams.getWindowingMode()); + taskFragment.setBounds(creationParams.getInitialBounds()); + mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment); + } + + void reparentTaskFragment(@NonNull WindowContainer oldParent, + @Nullable WindowContainer newParent, @Nullable IBinder errorCallbackToken) { + WindowContainer parent = newParent; + if (parent == null && oldParent.asTaskFragment() != null) { + parent = oldParent.asTaskFragment().getTask(); + } + if (parent == null) { + final Throwable exception = + new IllegalArgumentException("Not allowed to operate with invalid container"); + sendTaskFragmentOperationFailure(oldParent.asTaskFragment().getTaskFragmentOrganizer(), + errorCallbackToken, exception); + return; + } + while (oldParent.hasChild()) { + oldParent.getChildAt(0).reparent(parent, POSITION_TOP); + } + } + + private int deleteTaskFragment(@NonNull TaskFragment taskFragment, + @Nullable IBinder errorCallbackToken) { + final int index = mLaunchTaskFragments.indexOfValue(taskFragment); + if (index < 0) { + final Throwable exception = + new IllegalArgumentException("Not allowed to operate with invalid " + + "taskFragment"); + sendTaskFragmentOperationFailure(taskFragment.getTaskFragmentOrganizer(), + errorCallbackToken, exception); + return 0; + } + mLaunchTaskFragments.removeAt(index); + taskFragment.remove(true /* withTransition */, "deleteTaskFragment"); + return TRANSACT_EFFECTS_LIFECYCLE; + } + + @Nullable + TaskFragment getTaskFragment(IBinder tfToken) { + return mLaunchTaskFragments.get(tfToken); + } + + static class CallerInfo { + final int mPid; + final int mUid; + + CallerInfo() { + mPid = Binder.getCallingPid(); + mUid = Binder.getCallingUid(); + } + } + + void sendTaskFragmentOperationFailure(@NonNull ITaskFragmentOrganizer organizer, + @Nullable IBinder errorCallbackToken, @NonNull Throwable exception) { + if (organizer == null) { + throw new IllegalArgumentException("Not allowed to operate with invalid organizer"); + } + mService.mTaskFragmentOrganizerController + .onTaskFragmentError(organizer, errorCallbackToken, exception); + } + + private Throwable convertStartFailureToThrowable(int result, Intent intent) { + switch (result) { + case ActivityManager.START_INTENT_NOT_RESOLVED: + case ActivityManager.START_CLASS_NOT_FOUND: + return new ActivityNotFoundException("No Activity found to handle " + intent); + case ActivityManager.START_PERMISSION_DENIED: + return new SecurityException("Permission denied and not allowed to start activity " + + intent); + case ActivityManager.START_CANCELED: + return new AndroidRuntimeException("Activity could not be started for " + intent + + " with error code : " + result); + default: + return new AndroidRuntimeException("Start activity failed with error code : " + + result + " when starting " + intent); + } + } } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 1364c72e6275..3ccb06ccef15 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -25,6 +25,13 @@ import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.server.am.ActivityManagerService.MY_PID; +import static com.android.server.wm.ActivityRecord.State.DESTROYED; +import static com.android.server.wm.ActivityRecord.State.DESTROYING; +import static com.android.server.wm.ActivityRecord.State.PAUSED; +import static com.android.server.wm.ActivityRecord.State.PAUSING; +import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.ActivityRecord.State.STARTED; +import static com.android.server.wm.ActivityRecord.State.STOPPING; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE; @@ -32,13 +39,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; -import static com.android.server.wm.Task.ActivityState.DESTROYED; -import static com.android.server.wm.Task.ActivityState.DESTROYING; -import static com.android.server.wm.Task.ActivityState.PAUSED; -import static com.android.server.wm.Task.ActivityState.PAUSING; -import static com.android.server.wm.Task.ActivityState.RESUMED; -import static com.android.server.wm.Task.ActivityState.STARTED; -import static com.android.server.wm.Task.ActivityState.STOPPING; import android.Manifest; import android.annotation.NonNull; @@ -57,6 +57,7 @@ import android.content.res.Configuration; import android.os.Binder; import android.os.Build; import android.os.IBinder; +import android.os.LocaleList; import android.os.Message; import android.os.Process; import android.os.RemoteException; @@ -75,6 +76,7 @@ import com.android.server.wm.ActivityTaskManagerService.HotPath; import java.io.IOException; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -219,6 +221,10 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio /** Whether our process is currently running a {@link IRemoteAnimationRunner} */ private boolean mRunningRemoteAnimation; + /** List of "chained" processes that are running remote animations for this process */ + private final ArrayList<WeakReference<WindowProcessController>> mRemoteAnimationDelegates = + new ArrayList<>(); + // The bits used for mActivityStateFlags. private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16; private static final int ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED = 1 << 17; @@ -258,7 +264,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } onConfigurationChanged(atm.getGlobalConfiguration()); - mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mName); + mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mInfo.packageName); } public void setPid(int pid) { @@ -506,19 +512,19 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio */ @HotPath(caller = HotPath.START_SERVICE) public boolean areBackgroundFgsStartsAllowed() { - return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesAllowed(), + return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesState(), true /* isCheckingForFgsStart */); } - boolean areBackgroundActivityStartsAllowed(boolean appSwitchAllowed) { - return areBackgroundActivityStartsAllowed(appSwitchAllowed, + boolean areBackgroundActivityStartsAllowed(int appSwitchState) { + return areBackgroundActivityStartsAllowed(appSwitchState, false /* isCheckingForFgsStart */); } - private boolean areBackgroundActivityStartsAllowed(boolean appSwitchAllowed, + private boolean areBackgroundActivityStartsAllowed(int appSwitchState, boolean isCheckingForFgsStart) { return mBgLaunchController.areBackgroundActivityStartsAllowed(mPid, mUid, mInfo.packageName, - appSwitchAllowed, isCheckingForFgsStart, hasActivityInVisibleTask(), + appSwitchState, isCheckingForFgsStart, hasActivityInVisibleTask(), mInstrumentingWithBackgroundActivityStartPrivileges, mAtm.getLastStopAppSwitchesTime(), mLastActivityLaunchTime, mLastActivityFinishTime); @@ -725,20 +731,23 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio canUpdate = true; } - // Compare the z-order of ActivityStacks if both activities landed on same display. - if (display == topDisplay - && mPreQTopResumedActivity.getRootTask().compareTo( - activity.getRootTask()) <= 0) { - canUpdate = true; + // Update the topmost activity if the activity has higher z-order than the current + // top-resumed activity. + if (!canUpdate) { + final ActivityRecord ar = topDisplay.getActivity(r -> r == activity, + true /* traverseTopToBottom */, mPreQTopResumedActivity); + if (ar != null && ar != mPreQTopResumedActivity) { + canUpdate = true; + } } if (canUpdate) { // Make sure the previous top activity in the process no longer be resumed. if (mPreQTopResumedActivity != null && mPreQTopResumedActivity.isState(RESUMED)) { - final Task task = mPreQTopResumedActivity.getTask(); - if (task != null) { - boolean userLeaving = task.shouldBeVisible(null); - task.startPausingLocked(userLeaving, false /* uiSleeping */, + final TaskFragment taskFrag = mPreQTopResumedActivity.getTaskFragment(); + if (taskFrag != null) { + boolean userLeaving = taskFrag.shouldBeVisible(null); + taskFrag.startPausing(userLeaving, false /* uiSleeping */, activity, "top-resumed-changed"); } } @@ -809,10 +818,13 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return false; } - void updateNightModeForAllActivities(int nightMode) { + // TODO(b/199277065): Re-assess how app-specific locales are applied based on UXR + // TODO(b/199277729): Consider whether we need to add special casing for edge cases like + // activity-embeddings etc. + void updateAppSpecificSettingsForAllActivities(Integer nightMode, LocaleList localesOverride) { for (int i = mActivities.size() - 1; i >= 0; --i) { final ActivityRecord r = mActivities.get(i); - if (r.setOverrideNightMode(nightMode) && r.mVisibleRequested) { + if (r.applyAppSpecificConfig(nightMode, localesOverride) && r.mVisibleRequested) { r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */); } } @@ -940,7 +952,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio final int displayId = r.getDisplayId(); final Context c = root.getDisplayUiContext(displayId); - if (r.mVisibleRequested && !displayContexts.contains(c)) { + if (c != null && r.mVisibleRequested && !displayContexts.contains(c)) { displayContexts.add(c); } } @@ -991,7 +1003,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // Since there could be more than one activities in a process record, we don't need to // compute the OomAdj with each of them, just need to find out the activity with the // "best" state, the order would be visible, pausing, stopping... - Task.ActivityState bestInvisibleState = DESTROYED; + ActivityRecord.State bestInvisibleState = DESTROYED; boolean allStoppingFinishing = true; boolean visible = false; int minTaskLayer = Integer.MAX_VALUE; @@ -1098,7 +1110,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio void updateProcessInfo(boolean updateServiceConnectionActivities, boolean activityChange, boolean updateOomAdj, boolean addPendingTopUid) { if (addPendingTopUid) { - mAtm.mAmInternal.addPendingTopUid(mUid, mPid); + addToPendingTop(); } if (updateOomAdj) { prepareOomAdjustment(); @@ -1109,6 +1121,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio mAtm.mH.sendMessage(m); } + /** Makes the process have top state before oom-adj is computed from a posted message. */ + void addToPendingTop() { + mAtm.mAmInternal.addPendingTopUid(mUid, mPid); + } + void updateServiceConnectionActivities() { // Posting on handler so WM lock isn't held when we call into AM. mAtm.mH.sendMessage(PooledLambda.obtainMessage( @@ -1215,12 +1232,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio hasVisibleActivities = true; } - final Task task = r.getTask(); - if (task != null) { + final TaskFragment taskFragment = r.getTaskFragment(); + if (taskFragment != null) { // There may be a pausing activity that hasn't shown any window and was requested // to be hidden. But pausing is also a visible state, it should be regarded as // visible, so the caller can know the next activity should be resumed. - hasVisibleActivities |= task.handleAppDied(this); + hasVisibleActivities |= taskFragment.handleAppDied(this); } r.handleAppDied(); } @@ -1547,7 +1564,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // activity as it could lead to incorrect display metrics. For ex, IME services // expect their config to match the config of the display with the IME window // showing. + // If the configuration has been overridden by previous activity, empty it. mIsActivityConfigOverrideAllowed = false; + unregisterActivityConfigurationListener(); break; default: break; @@ -1596,11 +1615,38 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio updateRunningRemoteOrRecentsAnimation(); } + /** + * Marks another process as a "delegate" animator. This means that process is doing some part + * of a remote animation on behalf of this process. + */ + void addRemoteAnimationDelegate(WindowProcessController delegate) { + if (!isRunningRemoteTransition()) { + throw new IllegalStateException("Can't add a delegate to a process which isn't itself" + + " running a remote animation"); + } + mRemoteAnimationDelegates.add(new WeakReference<>(delegate)); + } + void updateRunningRemoteOrRecentsAnimation() { + if (!isRunningRemoteTransition()) { + // Clean-up any delegates + for (int i = 0; i < mRemoteAnimationDelegates.size(); ++i) { + final WindowProcessController delegate = mRemoteAnimationDelegates.get(i).get(); + if (delegate == null) continue; + delegate.setRunningRemoteAnimation(false); + delegate.setRunningRecentsAnimation(false); + } + mRemoteAnimationDelegates.clear(); + } + // Posting on handler so WM lock isn't held when we call into AM. mAtm.mH.sendMessage(PooledLambda.obtainMessage( WindowProcessListener::setRunningRemoteAnimation, mListener, - mRunningRecentsAnimation || mRunningRemoteAnimation)); + isRunningRemoteTransition())); + } + + boolean isRunningRemoteTransition() { + return mRunningRecentsAnimation || mRunningRemoteAnimation; } /** Adjusts scheduling group for animation. This method MUST NOT be called inside WM lock. */ diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 0af6a29fad10..a16d9c196b93 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -33,6 +33,7 @@ import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.SurfaceControl.Transaction; import static android.view.SurfaceControl.getGlobalTransaction; +import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; @@ -50,7 +51,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; import static android.view.WindowManager.LayoutParams.FLAG_SCALED; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; @@ -105,15 +105,17 @@ import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED; import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM; import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME; +import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER; +import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; -import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RESIZE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS; import static com.android.server.am.ActivityManagerService.MY_PID; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER; @@ -151,8 +153,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.WINDOW_STATE_BLAST_SYNC_TIMEOUT; import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION; -import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; -import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; @@ -191,6 +191,7 @@ import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityTaskManager; import android.app.AppOpsManager; import android.app.admin.DevicePolicyCache; import android.content.Context; @@ -200,6 +201,7 @@ import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.gui.TouchOcclusionMode; import android.os.Binder; import android.os.Build; import android.os.Debug; @@ -209,7 +211,6 @@ import android.os.PowerManager.WakeReason; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; -import android.os.TouchOcclusionMode; import android.os.Trace; import android.os.WorkSource; import android.provider.Settings; @@ -235,6 +236,7 @@ import android.view.InputWindowHandle; import android.view.InsetsSource; import android.view.InsetsState; import android.view.InsetsState.InternalInsetsType; +import android.view.InsetsVisibilities; import android.view.Surface; import android.view.Surface.Rotation; import android.view.SurfaceControl; @@ -270,7 +272,7 @@ import java.util.function.Predicate; /** A window in the window manager. */ class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState, - InsetsControlTarget { + InsetsControlTarget, InputTarget { static final String TAG = TAG_WITH_CLASS_NAME ? "WindowState" : TAG_WM; // The minimal size of a window within the usable area of the freeform root task. @@ -305,6 +307,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @NonNull WindowToken mToken; // The same object as mToken if this is an app window and null for non-app windows. ActivityRecord mActivityRecord; + /** Non-null if this is a starting window. */ + StartingData mStartingData; // mAttrs.flags is tested in animation without being locked. If the bits tested are ever // modified they will need to be locked. @@ -367,6 +371,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private boolean mRedrawForSyncReported; /** + * {@code true} when the client was still drawing for sync when the sync-set was finished or + * cancelled. This can happen if the window goes away during a sync. In this situation we need + * to make sure to still apply the postDrawTransaction when it finishes to prevent the client + * from getting stuck in a bad state. + */ + boolean mClientWasDrawingForSync = false; + + /** * Special mode that is intended only for the rounded corner overlay: during rotation * transition, we un-rotate the window token such that the window appears as it did before the * rotation. @@ -745,7 +757,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private boolean mIsDimming = false; private @Nullable InsetsSourceProvider mControllableInsetProvider; - private final InsetsState mRequestedInsetsState = new InsetsState(); + private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); /** * Freeze the insets state in some cases that not necessarily keeps up-to-date to the client. @@ -868,18 +880,23 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ @Override public boolean getRequestedVisibility(@InternalInsetsType int type) { - return mRequestedInsetsState.getSourceOrDefaultVisibility(type); + return mRequestedVisibilities.getVisibility(type); + } + + /** + * Returns all the requested visibilities. + * + * @return an {@link InsetsVisibilities} as the requested visibilities. + */ + InsetsVisibilities getRequestedVisibilities() { + return mRequestedVisibilities; } /** * @see #getRequestedVisibility(int) */ - void updateRequestedVisibility(InsetsState state) { - for (int i = 0; i < InsetsState.SIZE; i++) { - final InsetsSource source = state.peekSource(i); - if (source == null) continue; - mRequestedInsetsState.addSource(source); - } + void setRequestedVisibilities(InsetsVisibilities visibilities) { + mRequestedVisibilities.set(visibilities); } /** @@ -1157,7 +1174,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (WindowManager.LayoutParams.isSystemAlertWindowType(mAttrs.type)) { return TouchOcclusionMode.USE_OPACITY; } - if (isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_ALL)) { + if (isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_ALL) || inTransition()) { return TouchOcclusionMode.USE_OPACITY; } return TouchOcclusionMode.BLOCK_UNTRUSTED; @@ -1254,8 +1271,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP frame.inset(left, top, right, bottom); } - void computeFrameAndUpdateSourceFrame() { - computeFrame(); + void computeFrameAndUpdateSourceFrame(DisplayFrames displayFrames) { + computeFrame(displayFrames); // Update the source frame to provide insets to other windows during layout. If the // simulated frames exist, then this is not computing a stable result so just skip. if (mControllableInsetProvider != null && mSimulatedWindowFrames == null) { @@ -1266,7 +1283,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP /** * Perform standard frame computation. The result can be obtained with getFrame() if so desired. */ - void computeFrame() { + void computeFrame(DisplayFrames displayFrames) { if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) { // This window is being replaced and either already got information that it's being // removed or we are still waiting for some information. Because of this we don't @@ -1379,7 +1396,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final int fw = windowFrames.mFrame.width(); final int fh = windowFrames.mFrame.height(); - applyGravityAndUpdateFrame(windowFrames, layoutContainingFrame, layoutDisplayFrame); + applyGravityAndUpdateFrame(windowFrames, layoutContainingFrame, layoutDisplayFrame, + displayFrames); if (mAttrs.type == TYPE_DOCK_DIVIDER) { if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)) { @@ -1427,14 +1445,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } - // TODO: Look into whether this override is still necessary. @Override public Rect getBounds() { - if (mActivityRecord != null) { - return mActivityRecord.getBounds(); - } else { - return super.getBounds(); - } + // The window bounds are used for layout in screen coordinates. If the token has bounds for + // size compatibility mode, its configuration bounds are app based coordinates which should + // not be used for layout. + return mToken.hasSizeCompatBounds() ? mToken.getBounds() : super.getBounds(); } /** Retrieves the current frame of the window that the application sees. */ @@ -1472,6 +1488,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return mAttrs; } + WindowManager.LayoutParams getLayoutingAttrs(int rotation) { + if (!INSETS_LAYOUT_GENERALIZATION) { + return mAttrs; + } + final WindowManager.LayoutParams[] paramsForRotation = mAttrs.paramsForRotation; + if (paramsForRotation == null || paramsForRotation.length != 4 + || paramsForRotation[rotation] == null) { + return mAttrs; + } + return paramsForRotation[rotation]; + } + /** Retrieves the flags used to disable system UI functions. */ int getDisableFlags() { return mDisableFlags; @@ -1698,7 +1726,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return state; } - int getDisplayId() { + @Override + public int getDisplayId() { final DisplayContent displayContent = getDisplayContent(); if (displayContent == null) { return Display.INVALID_DISPLAY; @@ -1706,10 +1735,29 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return displayContent.getDisplayId(); } + @Override + public WindowState getWindowState() { + return this; + } + + @Override + public IWindow getIWindow() { + return mClient; + } + + @Override + public int getPid() { + return mSession.mPid; + } + Task getTask() { return mActivityRecord != null ? mActivityRecord.getTask() : null; } + @Nullable TaskFragment getTaskFragment() { + return mActivityRecord != null ? mActivityRecord.getTaskFragment() : null; + } + @Nullable Task getRootTask() { final Task task = getTask(); if (task != null) { @@ -1837,9 +1885,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return super.hasContentToDisplay(); } - @Override - boolean isVisible() { - return wouldBeVisibleIfPolicyIgnored() && isVisibleByPolicy() + private boolean isVisibleByPolicyOrInsets() { + return isVisibleByPolicy() // If we don't have a provider, this window isn't used as a window generating // insets, so nobody can hide it over the inset APIs. && (mControllableInsetProvider == null @@ -1847,11 +1894,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } @Override + boolean isVisible() { + return wouldBeVisibleIfPolicyIgnored() && isVisibleByPolicyOrInsets(); + } + + @Override boolean isVisibleRequested() { - if (shouldCheckTokenVisibleRequested()) { - return isVisible() && mToken.isVisibleRequested(); + final boolean localVisibleRequested = + wouldBeVisibleRequestedIfPolicyIgnored() && isVisibleByPolicyOrInsets(); + if (localVisibleRequested && shouldCheckTokenVisibleRequested()) { + return mToken.isVisibleRequested(); } - return isVisible(); + return localVisibleRequested; } /** @@ -1898,6 +1952,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return !isWallpaper || mToken.isVisible(); } + private boolean wouldBeVisibleRequestedIfPolicyIgnored() { + final WindowState parent = getParentWindow(); + final boolean isParentHiddenRequested = parent != null && !parent.isVisibleRequested(); + if (isParentHiddenRequested || mAnimatingExit || mDestroying) { + return false; + } + final boolean isWallpaper = mToken.asWallpaperToken() != null; + return !isWallpaper || mToken.isVisibleRequested(); + } + /** * Is this window visible, ignoring its app token? It is not visible if there is no surface, * or we are in the process of running an exit animation that will remove the surface. @@ -1928,10 +1992,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } /** - * Same as isVisible(), but we also count it as visible between the - * call to IWindowSession.add() and the first relayout(). + * Is this window capable of being visible (policy and content), in a visible part of the + * hierarchy, and, if an activity window, the activity is visible-requested. Note, this means + * if the activity is going-away, this will be {@code false} even when the window is visible. + * + * The 'adding' part refers to the period of time between IWindowSession.add() and the first + * relayout() -- which, for activities, is the same as visibleRequested. + * + * TODO(b/206005136): This is very similar to isVisibleRequested(). Investigate merging them. */ - boolean isVisibleOrAdding() { + boolean isVisibleRequestedOrAdding() { final ActivityRecord atoken = mActivityRecord; return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE)) && isVisibleByPolicy() && !isParentWindowHidden() @@ -2167,7 +2237,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWmService.mAccessibilityController; final int winTransit = TRANSIT_EXIT; mWinAnimator.applyAnimationLocked(winTransit, false /* isEntrance */); - if (accessibilityController != null) { + if (accessibilityController.hasCallbacks()) { accessibilityController.onWindowTransition(this, winTransit); } } @@ -2188,7 +2258,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } if (isVisibleNow() && animateExit) { mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false); - if (mWmService.mAccessibilityController != null) { + if (mWmService.mAccessibilityController.hasCallbacks()) { mWmService.mAccessibilityController.onWindowTransition(this, TRANSIT_EXIT); } changed = true; @@ -2238,7 +2308,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP startMoveAnimation(left, top); } - if (mWmService.mAccessibilityController != null) { + if (mWmService.mAccessibilityController.hasCallbacks()) { mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(getDisplayId()); } updateLocationInParentDisplayIfNeeded(); @@ -2276,7 +2346,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP && (mWindowFrames.mRelFrame.top != mWindowFrames.mLastRelFrame.top || mWindowFrames.mRelFrame.left != mWindowFrames.mLastRelFrame.left) && (!mIsChildWindow || !getParentWindow().hasMoved()) - && !mWmService.mAtmService.getTransitionController().isCollecting(); + && !mTransitionController.isCollecting(); } boolean isObscuringDisplay() { @@ -2362,6 +2432,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override void removeImmediately() { + if (!mRemoved) { + // Destroy surface before super call. The general pattern is that the children need + // to be removed before the parent (so that the sync-engine tracking works). Since + // WindowStateAnimator is a "virtual" child, we have to do it manually here. + mWinAnimator.destroySurfaceLocked(getSyncTransaction()); + } super.removeImmediately(); if (mRemoved) { @@ -2403,8 +2479,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP disposeInputChannel(); - mWinAnimator.destroySurfaceLocked(mTmpTransaction); - mTmpTransaction.apply(); mSession.windowRemovedLocked(); try { mClient.asBinder().unlinkToDeath(mDeathRecipient, 0); @@ -2520,13 +2594,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP setDisplayLayoutNeeded(); mWmService.requestTraversal(); } - if (mWmService.mAccessibilityController != null) { + if (mWmService.mAccessibilityController.hasCallbacks()) { mWmService.mAccessibilityController.onWindowTransition(this, transit); } } final boolean isAnimating = mAnimatingExit - || isAnimating(TRANSITION | PARENTS, EXIT_ANIMATING_TYPES) - && (mActivityRecord == null || !mActivityRecord.isWaitingForTransitionStart()); + || isAnimating(TRANSITION | PARENTS, EXIT_ANIMATING_TYPES); final boolean lastWindowIsStartingWindow = startingWindow && mActivityRecord != null && mActivityRecord.isLastWindow(this); // We delay the removal of a window if it has a showing surface that can be used to run @@ -2657,9 +2730,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } + // Don't allow transient-launch activities to take IME. + if (rootTask != null && mActivityRecord != null + && mTransitionController.isTransientLaunch(mActivityRecord)) { + return false; + } + if (DEBUG_INPUT_METHOD) { - Slog.i(TAG_WM, "isVisibleOrAdding " + this + ": " + isVisibleOrAdding()); - if (!isVisibleOrAdding()) { + Slog.i(TAG_WM, "isVisibleRequestedOrAdding " + this + ": " + + isVisibleRequestedOrAdding()); + if (!isVisibleRequestedOrAdding()) { Slog.i(TAG_WM, " mSurfaceController=" + mWinAnimator.mSurfaceController + " relayoutCalled=" + mRelayoutCalled + " viewVis=" + mViewVisibility @@ -2673,7 +2753,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } } - return isVisibleOrAdding(); + return isVisibleRequestedOrAdding(); } private final class DeadWindowEventReceiver extends InputEventReceiver { @@ -2801,10 +2881,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } - int getSurfaceTouchableRegion(Region region, int flags) { - final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0; + void getSurfaceTouchableRegion(Region region, WindowManager.LayoutParams attrs) { + final boolean modal = attrs.isModal(); if (modal) { - flags |= FLAG_NOT_TOUCH_MODAL; if (mActivityRecord != null) { // Limit the outer touch to the activity root task region. updateRegionForModalActivityWindow(region); @@ -2836,8 +2915,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mInvGlobalScale != 1.f) { region.scale(mInvGlobalScale); } - - return flags; } /** @@ -2880,9 +2957,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // means we need to intercept touches outside of that window. The dim layer // user associated with the window (task or root task) will give us the good // bounds, as they would be used to display the dim layer. - final Task task = getTask(); - if (task != null) { - task.getDimBounds(mTmpRect); + final TaskFragment taskFragment = getTaskFragment(); + if (taskFragment != null) { + final Task task = taskFragment.asTask(); + if (task != null) { + task.getDimBounds(mTmpRect); + } else { + mTmpRect.set(taskFragment.getBounds()); + } } else if (getRootTask() != null) { getRootTask().getDimBounds(mTmpRect); } @@ -3094,7 +3176,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP public String canReceiveKeysReason(boolean fromUserTouch) { return "fromTouch= " + fromUserTouch - + " isVisibleOrAdding=" + isVisibleOrAdding() + + " isVisibleRequestedOrAdding=" + isVisibleRequestedOrAdding() + " mViewVisibility=" + mViewVisibility + " mRemoveOnExit=" + mRemoveOnExit + " flags=" + mAttrs.flags @@ -3106,7 +3188,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } public boolean canReceiveKeys(boolean fromUserTouch) { - final boolean canReceiveKeys = isVisibleOrAdding() + final boolean canReceiveKeys = isVisibleRequestedOrAdding() && (mViewVisibility == View.VISIBLE) && !mRemoveOnExit && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0) && (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch)) @@ -3372,7 +3454,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } public void pokeDrawLockLw(long timeout) { - if (isVisibleOrAdding()) { + if (isVisibleRequestedOrAdding()) { if (mDrawLock == null) { // We want the tag name to be somewhat stable so that it is easier to correlate // in wake lock statistics. So in particular, we don't want to include the @@ -3532,10 +3614,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } // Exclude toast because legacy apps may show toast window by themselves, so the misused // apps won't always be considered as foreground state. - if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST) { + // Exclude private presentations as they can only be shown on private virtual displays and + // shouldn't be the cause of an app be considered foreground. + if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST + && mAttrs.type != TYPE_PRIVATE_PRESENTATION) { mWmService.mAtmService.mActiveUids.onNonAppSurfaceVisibilityChanged(mOwnerUid, shown); } - if (mIsImWindow && mWmService.mAccessibilityController != null) { + if (mIsImWindow && mWmService.mAccessibilityController.hasCallbacks()) { mWmService.mAccessibilityController.onImeSurfaceShownChanged(this, shown); } } @@ -3680,10 +3765,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * {@link WindowManager.LayoutParams#FLAG_NOT_TOUCH_MODAL touch modality.} */ void getEffectiveTouchableRegion(Region outRegion) { - final boolean modal = (mAttrs.flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0; final DisplayContent dc = getDisplayContent(); - if (modal && dc != null) { + if (mAttrs.isModal() && dc != null) { outRegion.set(dc.getBounds()); cropRegionToRootTaskBoundsIfNeeded(outRegion); subtractTouchExcludeRegionIfNeeded(outRegion); @@ -3865,7 +3949,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final boolean forceRelayout = syncRedraw || reportOrientation || isDragResizeChanged(); final DisplayContent displayContent = getDisplayContent(); final boolean alwaysConsumeSystemBars = - displayContent.getDisplayPolicy().areSystemBarsForcedShownLw(this); + displayContent.getDisplayPolicy().areSystemBarsForcedShownLw(); final int displayId = displayContent.getDisplayId(); markRedrawForSyncReported(); @@ -3879,7 +3963,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP "Requested redraw for orientation change: %s", this); } - if (mWmService.mAccessibilityController != null) { + if (mWmService.mAccessibilityController.hasCallbacks()) { mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId); } updateLocationInParentDisplayIfNeeded(); @@ -3931,7 +4015,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * Called when the insets state changed. */ void notifyInsetsChanged() { - ProtoLog.d(WM_DEBUG_IME, "notifyInsetsChanged for %s ", this); + ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsChanged for %s ", this); try { mClient.insetsChanged(getCompatInsetsState(), hasMoved(), @@ -3943,7 +4027,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public void notifyInsetsControlChanged() { - ProtoLog.d(WM_DEBUG_IME, "notifyInsetsControlChanged for %s ", this); + ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsControlChanged for %s ", this); if (mAppDied || mRemoved) { return; } @@ -4364,9 +4448,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP pw.println(prefix + "mEmbeddedDisplayContents=" + mEmbeddedDisplayContents); } if (dumpAll) { - final String visibilityString = mRequestedInsetsState.toSourceVisibilityString(); + final String visibilityString = mRequestedVisibilities.toString(); if (!visibilityString.isEmpty()) { - pw.println(prefix + "Requested visibility: " + visibilityString); + pw.println(prefix + "Requested visibilities: " + visibilityString); } } } @@ -4399,12 +4483,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } private void applyGravityAndUpdateFrame(WindowFrames windowFrames, Rect containingFrame, - Rect displayFrame) { + Rect displayFrame, DisplayFrames displayFrames) { final int pw = containingFrame.width(); final int ph = containingFrame.height(); final Task task = getTask(); final boolean inNonFullscreenContainer = !inAppWindowThatMatchesParentBounds(); - final boolean noLimits = (mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0; + final WindowManager.LayoutParams attrs = getLayoutingAttrs(displayFrames.mRotation); + final boolean noLimits = (attrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0; // We need to fit it to the display if either // a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen @@ -4414,49 +4499,54 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // screen, but SurfaceViews want to be always at a specific location so we don't fit it to // the display. final boolean fitToDisplay = (task == null || !inNonFullscreenContainer) - || ((mAttrs.type != TYPE_BASE_APPLICATION) && !noLimits); + || ((attrs.type != TYPE_BASE_APPLICATION) && !noLimits); float x, y; int w,h; final boolean hasCompatScale = hasCompatScale(); - if ((mAttrs.flags & FLAG_SCALED) != 0) { - if (mAttrs.width < 0) { + if ((attrs.flags & FLAG_SCALED) != 0 || mAttrs != attrs) { + // For the window with different layout attrs for different rotations, we need to avoid + // using requested size. Otherwise, when finishing a simulated rotation, the information + // coming from WindowManagerServices to the ViewRootImpl may not contain the correct + // value for the new rotation, and there will be a quick flash of wrong layout when the + // simulated activity faded out. + if (attrs.width < 0) { w = pw; } else if (hasCompatScale) { - w = (int)(mAttrs.width * mGlobalScale + .5f); + w = (int) (attrs.width * mGlobalScale + .5f); } else { - w = mAttrs.width; + w = attrs.width; } - if (mAttrs.height < 0) { + if (attrs.height < 0) { h = ph; } else if (hasCompatScale) { - h = (int)(mAttrs.height * mGlobalScale + .5f); + h = (int) (attrs.height * mGlobalScale + .5f); } else { - h = mAttrs.height; + h = attrs.height; } } else { - if (mAttrs.width == MATCH_PARENT) { + if (attrs.width == MATCH_PARENT) { w = pw; } else if (hasCompatScale) { - w = (int)(mRequestedWidth * mGlobalScale + .5f); + w = (int) (mRequestedWidth * mGlobalScale + .5f); } else { w = mRequestedWidth; } - if (mAttrs.height == MATCH_PARENT) { + if (attrs.height == MATCH_PARENT) { h = ph; } else if (hasCompatScale) { - h = (int)(mRequestedHeight * mGlobalScale + .5f); + h = (int) (mRequestedHeight * mGlobalScale + .5f); } else { h = mRequestedHeight; } } if (hasCompatScale) { - x = mAttrs.x * mGlobalScale; - y = mAttrs.y * mGlobalScale; + x = attrs.x * mGlobalScale; + y = attrs.y * mGlobalScale; } else { - x = mAttrs.x; - y = mAttrs.y; + x = attrs.x; + y = attrs.y; } if (inNonFullscreenContainer && !layoutInParentFrame()) { @@ -4483,13 +4573,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } // Set mFrame - Gravity.apply(mAttrs.gravity, w, h, containingFrame, - (int) (x + mAttrs.horizontalMargin * pw), - (int) (y + mAttrs.verticalMargin * ph), windowFrames.mFrame); - + Gravity.apply(attrs.gravity, w, h, containingFrame, + (int) (x + attrs.horizontalMargin * pw), + (int) (y + attrs.verticalMargin * ph), windowFrames.mFrame); // Now make sure the window fits in the overall display frame. if (fitToDisplay) { - Gravity.applyDisplay(mAttrs.gravity, displayFrame, windowFrames.mFrame); + Gravity.applyDisplay(attrs.gravity, displayFrame, windowFrames.mFrame); } // We need to make sure we update the CompatFrame as it is used for @@ -4685,7 +4774,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final int drawState = mWinAnimator.mDrawState; if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) { if (mAttrs.type != TYPE_APPLICATION_STARTING) { - mActivityRecord.onFirstWindowDrawn(this, mWinAnimator); + mActivityRecord.onFirstWindowDrawn(this); } else { mActivityRecord.onStartingWindowDrawn(); } @@ -4773,6 +4862,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP windowInfo.focused = isFocused(); Task task = getTask(); windowInfo.inPictureInPicture = (task != null) && task.inPinnedWindowingMode(); + windowInfo.taskId = task == null ? ActivityTaskManager.INVALID_TASK_ID : task.mTaskId; windowInfo.hasFlagWatchOutsideTouch = (mAttrs.flags & WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH) != 0; @@ -4882,20 +4972,27 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private boolean applyImeWindowsIfNeeded(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) { - // If this window is the current IME target, so we need to process the IME windows - // directly above it. The exception is if we are in split screen - // in which case we process the IME at the DisplayContent level to + // No need to apply to IME window if the window is not the current IME layering target. + if (!isImeLayeringTarget()) { + return false; + } + // If we are in split screen which case we process the IME at the DisplayContent level to // ensure it is above the docked divider. - // (i.e. Like {@link DisplayContent.ImeContainer#skipImeWindowsDuringTraversal}, the IME - // window will be ignored to traverse when the IME target is still in split-screen mode). - if (isImeLayeringTarget() - && (!mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated() - || getTask() == null)) { - if (mDisplayContent.forAllImeWindows(callback, traverseTopToBottom)) { - return true; - } + // i.e. Like {@link DisplayContent.ImeContainer#skipImeWindowsDuringTraversal}, the IME + // window will be ignored to traverse when the IME target is still in split-screen mode. + if (mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated() + && getTask() != null) { + return false; } - return false; + // Note that we don't process IME window if the IME input target is not on the screen. + // In case some unexpected IME visibility cases happen like starting the remote + // animation on the keyguard but seeing the IME window that originally on the app + // which behinds the keyguard. + final WindowState imeInputTarget = getImeInputTarget(); + if (imeInputTarget != null && !(imeInputTarget.isDrawn() || imeInputTarget.isVisible())) { + return false; + } + return mDisplayContent.forAllImeWindows(callback, traverseTopToBottom); } private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> callback, @@ -4970,6 +5067,48 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return false; } + private boolean shouldFinishAnimatingExit() { + // Exit animation might be applied soon. + if (inTransition()) { + ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "shouldWaitAnimatingExit: isTransition: %s", + this); + return false; + } + if (!mDisplayContent.okToAnimate()) { + return true; + } + // Exit animation is running. + if (isAnimating(TRANSITION | PARENTS, EXIT_ANIMATING_TYPES)) { + ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "shouldWaitAnimatingExit: isAnimating: %s", + this); + return false; + } + // If the wallpaper is currently behind this app window, we need to change both of + // them inside of a transaction to avoid artifacts. + if (mDisplayContent.mWallpaperController.isWallpaperTarget(this)) { + ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, + "shouldWaitAnimatingExit: isWallpaperTarget: %s", this); + return false; + } + return true; + } + + /** + * If this is window is stuck in the animatingExit status, resume clean up procedure blocked + * by the exit animation. + */ + void cleanupAnimatingExitWindow() { + // TODO(b/205335975): WindowManagerService#tryStartExitingAnimation starts an exit animation + // and set #mAnimationExit. After the exit animation finishes, #onExitAnimationDone shall + // be called, but there seems to be a case that #onExitAnimationDone is not triggered, so + // a windows stuck in the animatingExit status. + if (mAnimatingExit && shouldFinishAnimatingExit()) { + ProtoLog.w(WM_DEBUG_APP_TRANSITIONS, "Clear window stuck on animatingExit status: %s", + this); + onExitAnimationDone(); + } + } + void onExitAnimationDone() { if (DEBUG_ANIM) Slog.v(TAG, "onExitAnimationDone in " + this + ": exiting=" + mAnimatingExit + " remove=" + mRemoveOnExit @@ -4999,7 +5138,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (isAnimating()) { return; } - if (mWmService.mAccessibilityController != null) { + if (mWmService.mAccessibilityController.hasCallbacks()) { mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(getDisplayId()); } @@ -5416,6 +5555,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return mWillReplaceWindow; } + private boolean isStartingWindowAssociatedToTask() { + return mStartingData != null && mStartingData.mAssociatedTask != null; + } + private void applyDims() { if (!mAnimatingExit && mAppDied) { mIsDimming = true; @@ -5565,7 +5708,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP outPoint.offset(-parent.mWindowFrames.mFrame.left + mTmpPoint.x, -parent.mWindowFrames.mFrame.top + mTmpPoint.y); } else if (parentWindowContainer != null) { - final Rect parentBounds = parentWindowContainer.getBounds(); + final Rect parentBounds = isStartingWindowAssociatedToTask() + ? mStartingData.mAssociatedTask.getBounds() + : parentWindowContainer.getBounds(); outPoint.offset(-parentBounds.left, -parentBounds.top); } @@ -5602,9 +5747,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } boolean needsRelativeLayeringToIme() { - // We only use the relative layering mode in split screen, as part of elevating the IME - // and windows above it's target above the docked divider. - if (!inSplitScreenWindowingMode()) { + // We use the relative layering when IME isn't attached to the app. Such as part of + // elevating the IME and windows above it's target above the docked divider in + // split-screen, or make the popupMenu to be above the IME when the parent window is the + // IME layering target in bubble/freeform mode. + if (mDisplayContent.shouldImeAttachedToApp()) { return false; } @@ -5648,6 +5795,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override void assignLayer(Transaction t, int layer) { + if (isStartingWindowAssociatedToTask()) { + // The starting window should cover the task. + t.setLayer(mSurfaceControl, Integer.MAX_VALUE); + return; + } // See comment in assignRelativeLayerForImeTargetChild if (needsRelativeLayeringToIme()) { getDisplayContent().assignRelativeLayerForImeTargetChild(t, this); @@ -5660,6 +5812,24 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return mIsDimming; } + @Override + protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) { + if (isStartingWindowAssociatedToTask()) { + // Its surface is already put in task. Don't reparent when transferring starting window + // across activities. + return; + } + super.reparentSurfaceControl(t, newParent); + } + + @Override + public SurfaceControl getAnimationLeashParent() { + if (isStartingWindowAssociatedToTask()) { + return mStartingData.mAssociatedTask.mSurfaceControl; + } + return super.getAnimationLeashParent(); + } + // TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA // then we can drop all negative layering on the windowing side and simply inherit // the default implementation here. @@ -5899,9 +6069,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP outSurfaceInsets.set(getAttrs().surfaceInsets); final InsetsState state = getInsetsStateWithVisibilityOverride(); outInsets.set(state.calculateInsets(outFrame, systemBars(), - false /* ignoreVisibility */)); + false /* ignoreVisibility */).toRect()); outStableInsets.set(state.calculateInsets(outFrame, systemBars(), - true /* ignoreVisibility */)); + true /* ignoreVisibility */).toRect()); } void setViewVisibility(int viewVisibility) { @@ -5921,17 +6091,32 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // since a generic WindowContainer only needs to wait for its // children to finish and is immediately ready from its own // perspective but at the WindowState level we need to wait for ourselves - // to draw even if the children draw first our don't need to sync, so we start + // to draw even if the children draw first or don't need to sync, so we start // in WAITING state rather than READY. mSyncState = SYNC_STATE_WAITING_FOR_DRAW; requestRedrawForSync(); - - mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this); - mWmService.mH.sendNewMessageDelayed(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this, - BLAST_TIMEOUT_DURATION); return true; } + @Override + boolean isSyncFinished() { + if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mViewVisibility == View.GONE + && !isVisibleRequested()) { + // Don't wait for GONE windows. However, we don't alter the state in case the window + // becomes un-gone while the syncset is still active. + return true; + } + return super.isSyncFinished(); + } + + @Override + void finishSync(Transaction outMergedTransaction, boolean cancel) { + if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mRedrawForSyncReported) { + mClientWasDrawingForSync = true; + } + super.finishSync(outMergedTransaction, cancel); + } + boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction) { if (mOrientationChangeRedrawRequestTime > 0) { final long duration = @@ -5947,15 +6132,25 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } executeDrawHandlers(postDrawTransaction); + + final boolean applyPostDrawNow = mClientWasDrawingForSync && postDrawTransaction != null; + mClientWasDrawingForSync = false; if (!onSyncFinishedDrawing()) { - return mWinAnimator.finishDrawingLocked(postDrawTransaction); + return mWinAnimator.finishDrawingLocked(postDrawTransaction, applyPostDrawNow); + } + + if (mActivityRecord != null + && mTransitionController.isShellTransitionsEnabled() + && mAttrs.type == TYPE_APPLICATION_STARTING) { + mWmService.mAtmService.mTaskSupervisor.getActivityMetricsLogger() + .notifyStartingWindowDrawn(mActivityRecord); } if (postDrawTransaction != null) { mSyncTransaction.merge(postDrawTransaction); } - mWinAnimator.finishDrawingLocked(null); + mWinAnimator.finishDrawingLocked(null, false /* forceApplyNow */); // We always want to force a traversal after a finish draw for blast sync. return true; } @@ -5993,8 +6188,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } boolean hasWallpaper() { - return (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0 - || (mActivityRecord != null && mActivityRecord.hasWallpaperBackgroudForLetterbox()); + return (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0 || hasWallpaperForLetterboxBackground(); + } + + boolean hasWallpaperForLetterboxBackground() { + return mActivityRecord != null && mActivityRecord.hasWallpaperBackgroudForLetterbox(); } /** diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 80941961cc5a..423b3a05565e 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -41,7 +41,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; import static com.android.server.wm.WindowManagerService.logWithStack; import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE; import static com.android.server.wm.WindowStateAnimatorProto.SURFACE; @@ -71,7 +70,6 @@ import java.io.PrintWriter; **/ class WindowStateAnimator { static final String TAG = TAG_WITH_CLASS_NAME ? "WindowStateAnimator" : TAG_WM; - static final int WINDOW_FREEZE_LAYER = TYPE_LAYER_MULTIPLIER * 200; static final int PRESERVED_SURFACE_LAYER = 1; /** @@ -82,16 +80,10 @@ class WindowStateAnimator { static final int ROOT_TASK_CLIP_AFTER_ANIM = 0; /** - * Mode how the window gets clipped by the root task bounds: The clipping should be applied - * before applying the animation transformation, i.e. the root task bounds move with the window. - */ - static final int ROOT_TASK_CLIP_BEFORE_ANIM = 1; - - /** * Mode how window gets clipped by the root task bounds during an animation: Don't clip the * window by the root task bounds. */ - static final int ROOT_TASK_CLIP_NONE = 2; + static final int ROOT_TASK_CLIP_NONE = 1; // Unchanging local convenience fields. final WindowManagerService mService; @@ -230,7 +222,8 @@ class WindowStateAnimator { } } - boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) { + boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction, + boolean forceApplyNow) { final boolean startingWindow = mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; if (startingWindow) { @@ -255,12 +248,12 @@ class WindowStateAnimator { // If there is no surface, the last draw was for the previous surface. We don't want to // wait until the new surface is shown and instead just apply the transaction right // away. - if (mLastHidden && mDrawState != NO_SURFACE) { + if (mLastHidden && mDrawState != NO_SURFACE && !forceApplyNow) { mPostDrawTransaction.merge(postDrawTransaction); - layoutNeeded = true; } else { - postDrawTransaction.apply(); + mWin.getSyncTransaction().merge(postDrawTransaction); } + layoutNeeded = true; } return layoutNeeded; @@ -684,7 +677,7 @@ class WindowStateAnimator { applyAnimationLocked(transit, true); } - if (mService.mAccessibilityController != null) { + if (mService.mAccessibilityController.hasCallbacks()) { mService.mAccessibilityController.onWindowTransition(mWin, transit); } } diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 3cbc67c004cd..ad351f099e1f 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -16,9 +16,11 @@ package com.android.server.wm; +import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; @@ -230,6 +232,11 @@ class WindowToken extends WindowContainer<WindowState> { ProtoLog.w(WM_DEBUG_WINDOW_MOVEMENT, "removeAllWindowsIfPossible: removing win=%s", win); win.removeIfPossible(); + if (i > mChildren.size()) { + // It's possible for removeIfPossible to delete siblings (for example if it is a + // starting window, it will perform operations on the ActivityRecord). + i = mChildren.size(); + } } } @@ -453,9 +460,24 @@ class WindowToken extends WindowContainer<WindowState> { } Rect getFixedRotationBarContentFrame(int windowType) { - return isFixedRotationTransforming() - ? mFixedRotationTransformState.mBarContentFrames.get(windowType) - : null; + if (!isFixedRotationTransforming()) { + return null; + } + if (!INSETS_LAYOUT_GENERALIZATION) { + return mFixedRotationTransformState.mBarContentFrames.get(windowType); + } + final DisplayFrames displayFrames = mFixedRotationTransformState.mDisplayFrames; + final Rect tmpRect = new Rect(); + if (windowType == TYPE_NAVIGATION_BAR) { + tmpRect.set(displayFrames.mInsetsState.getSource(InsetsState.ITYPE_NAVIGATION_BAR) + .getFrame()); + } + if (windowType == TYPE_STATUS_BAR) { + tmpRect.set(displayFrames.mInsetsState.getSource(InsetsState.ITYPE_STATUS_BAR) + .getFrame()); + } + tmpRect.intersect(displayFrames.mDisplayCutoutSafe); + return tmpRect; } InsetsState getFixedRotationTransformInsetsState() { diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java index 0bb97f560a1c..6204824d70a9 100644 --- a/services/core/java/com/android/server/wm/WindowTracing.java +++ b/services/core/java/com/android/server/wm/WindowTracing.java @@ -54,7 +54,8 @@ class WindowTracing { private static final int BUFFER_CAPACITY_CRITICAL = 512 * 1024; private static final int BUFFER_CAPACITY_TRIM = 2048 * 1024; private static final int BUFFER_CAPACITY_ALL = 4096 * 1024; - private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace.pb"; + static final String WINSCOPE_EXT = ".winscope"; + private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace" + WINSCOPE_EXT; private static final String TAG = "WindowTracing"; private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp index 4190a91710fc..94bc22a05d7a 100644 --- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp +++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp @@ -59,6 +59,9 @@ using android::base::unique_fd; namespace android { +static bool cancelRunningCompaction; +static bool compactionInProgress; + // Legacy method for compacting processes, any new code should // use compactProcess instead. static inline void compactProcessProcfs(int pid, const std::string& compactionType) { @@ -83,9 +86,18 @@ static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseT // Skip compaction if failed to open pidfd with any error return -errno; } + compactionInProgress = true; + cancelRunningCompaction = false; int64_t totalBytesCompacted = 0; for (int iBase = 0; iBase < vmas.size(); iBase += UIO_MAXIOV) { + if (CC_UNLIKELY(cancelRunningCompaction)) { + // There could be a significant delay betweenwhen a compaction + // is requested and when it is handled during this time + // our OOM adjust could have improved. + cancelRunningCompaction = false; + break; + } int totalVmasToKernel = std::min(UIO_MAXIOV, (int)(vmas.size() - iBase)); for (int iVec = 0, iVma = iBase; iVec < totalVmasToKernel; ++iVec, ++iVma) { vmasToKernel[iVec].iov_base = (void*)vmas[iVma].start; @@ -95,11 +107,13 @@ static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseT auto bytesCompacted = process_madvise(pidfd, vmasToKernel, totalVmasToKernel, madviseType, 0); if (CC_UNLIKELY(bytesCompacted == -1)) { + compactionInProgress = false; return -errno; } totalBytesCompacted += bytesCompacted; } + compactionInProgress = false; return totalBytesCompacted; } @@ -228,6 +242,12 @@ static void com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *, job } } +static void com_android_server_am_CachedAppOptimizer_cancelCompaction(JNIEnv*, jobject) { + if (compactionInProgress) { + cancelRunningCompaction = true; + } +} + static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, jobject, jint pid, jint compactionFlags) { compactProcessOrFallback(pid, compactionFlags); @@ -279,6 +299,8 @@ static jstring com_android_server_am_CachedAppOptimizer_getFreezerCheckPath(JNIE static const JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ + {"cancelCompaction", "()V", + (void*)com_android_server_am_CachedAppOptimizer_cancelCompaction}, {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem}, {"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess}, {"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder}, diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp index d43cf3f59170..6a50d3834355 100644 --- a/services/core/xsd/Android.bp +++ b/services/core/xsd/Android.bp @@ -14,7 +14,6 @@ xsd_config { package_name: "com.android.server.pm.permission.configfile", } - xsd_config { name: "platform-compat-config", srcs: ["platform-compat/config/platform-compat-config.xsd"], @@ -42,6 +41,7 @@ xsd_config { srcs: ["display-layout-config/display-layout-config.xsd"], api_dir: "display-layout-config/schema", package_name: "com.android.server.display.config.layout", + boolean_getter: true, } xsd_config { diff --git a/services/core/xsd/device-state-config/device-state-config.xsd b/services/core/xsd/device-state-config/device-state-config.xsd index 94a398f2cdb7..86f41769008d 100644 --- a/services/core/xsd/device-state-config/device-state-config.xsd +++ b/services/core/xsd/device-state-config/device-state-config.xsd @@ -40,10 +40,19 @@ <xs:element name="name" type="xs:string" minOccurs="0"> <xs:annotation name="nullable" /> </xs:element> + <xs:element name="flags" type="flags" /> <xs:element name="conditions" type="conditions" /> </xs:sequence> </xs:complexType> + <xs:complexType name="flags"> + <xs:sequence> + <xs:element name="flag" type="xs:string" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation name="nullable" /> + </xs:element> + </xs:sequence> + </xs:complexType> + <xs:complexType name="conditions"> <xs:sequence> <xs:element name="lid-switch" type="lidSwitchCondition" minOccurs="0"> diff --git a/services/core/xsd/device-state-config/schema/current.txt b/services/core/xsd/device-state-config/schema/current.txt index 08fccf8ad949..a98d4e569cd6 100644 --- a/services/core/xsd/device-state-config/schema/current.txt +++ b/services/core/xsd/device-state-config/schema/current.txt @@ -11,9 +11,11 @@ package com.android.server.policy.devicestate.config { public class DeviceState { ctor public DeviceState(); method public com.android.server.policy.devicestate.config.Conditions getConditions(); + method public com.android.server.policy.devicestate.config.Flags getFlags(); method public java.math.BigInteger getIdentifier(); method @Nullable public String getName(); method public void setConditions(com.android.server.policy.devicestate.config.Conditions); + method public void setFlags(com.android.server.policy.devicestate.config.Flags); method public void setIdentifier(java.math.BigInteger); method public void setName(@Nullable String); } @@ -23,6 +25,11 @@ package com.android.server.policy.devicestate.config { method public java.util.List<com.android.server.policy.devicestate.config.DeviceState> getDeviceState(); } + public class Flags { + ctor public Flags(); + method @Nullable public java.util.List<java.lang.String> getFlag(); + } + public class LidSwitchCondition { ctor public LidSwitchCondition(); method public boolean getOpen(); diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd index c542c0d0c382..e14139a0860a 100644 --- a/services/core/xsd/display-layout-config/display-layout-config.xsd +++ b/services/core/xsd/display-layout-config/display-layout-config.xsd @@ -52,6 +52,6 @@ <xs:element name="address" type="xs:nonNegativeInteger"/> </xs:sequence> <xs:attribute name="enabled" type="xs:boolean" use="optional" /> - <xs:attribute name="isDefault" type="xs:boolean" use="optional" /> + <xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" /> </xs:complexType> </xs:schema> diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt index 817188509f81..f3915754a1a4 100644 --- a/services/core/xsd/display-layout-config/schema/current.txt +++ b/services/core/xsd/display-layout-config/schema/current.txt @@ -4,11 +4,11 @@ package com.android.server.display.config.layout { public class Display { ctor public Display(); method public java.math.BigInteger getAddress(); - method public boolean getEnabled(); - method public boolean getIsDefault(); + method public boolean isDefaultDisplay(); + method public boolean isEnabled(); method public void setAddress(java.math.BigInteger); + method public void setDefaultDisplay(boolean); method public void setEnabled(boolean); - method public void setIsDefault(boolean); } public class Layout { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java index 8ea21ec74ad6..a3017990543f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java @@ -46,6 +46,12 @@ public class DevicePolicyCacheImpl extends DevicePolicyCache { @GuardedBy("mLock") private final SparseIntArray mPermissionPolicy = new SparseIntArray(); + /** Maps to {@code ActiveAdmin.mAdminCanGrantSensorsPermissions}. + * + * <p>For users affiliated with the device, they inherit the policy from {@code DO} so + * it will map to the {@code DO}'s policy. Otherwise it will map to the admin of the requesting + * user. + */ @GuardedBy("mLock") private final SparseBooleanArray mCanGrantSensorsPermissions = new SparseBooleanArray(); @@ -102,17 +108,16 @@ public class DevicePolicyCacheImpl extends DevicePolicyCache { } @Override - public boolean canAdminGrantSensorsPermissionsForUser(@UserIdInt int userHandle) { + public boolean canAdminGrantSensorsPermissionsForUser(@UserIdInt int userId) { synchronized (mLock) { - return mCanGrantSensorsPermissions.get(userHandle, false); + return mCanGrantSensorsPermissions.get(userId, false); } } /** Sets ahmin control over permission grants for user. */ - public void setAdminCanGrantSensorsPermissions(@UserIdInt int userHandle, - boolean canGrant) { + public void setAdminCanGrantSensorsPermissions(@UserIdInt int userId, boolean canGrant) { synchronized (mLock) { - mCanGrantSensorsPermissions.put(userHandle, canGrant); + mCanGrantSensorsPermissions.put(userId, canGrant); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 99e28d101496..774a485aac6b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -258,6 +258,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.os.UserManager.UserRestrictionSource; import android.os.storage.StorageManager; import android.permission.AdminPermissionControlParams; import android.permission.IPermissionManager; @@ -286,6 +287,7 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.DebugUtils; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Pair; @@ -327,7 +329,6 @@ import com.android.server.PersistentDataBlockManagerInternal; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo; -import com.android.server.devicepolicy.Owners.OwnerDto; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.pm.RestrictionsSet; @@ -698,6 +699,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private DevicePolicyConstants mConstants; + /** + * User to be switched to on {@code logoutUser()}. + * + * <p>Only used on devices with headless system user mode + */ + @GuardedBy("getLockObject()") + private @UserIdInt int mLogoutUserId = UserHandle.USER_NULL; + + /** + * User the network logging notification was sent to. + */ + // Guarded by mHandler + private @UserIdInt int mNetworkLoggingNotificationUserId = UserHandle.USER_NULL; + private static final boolean ENABLE_LOCK_GUARD = true; /** @@ -1259,17 +1274,37 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } // Used by DevicePolicyManagerServiceShellCommand - List<OwnerDto> listAllOwners() { + List<OwnerShellData> listAllOwners() { Preconditions.checkCallAuthorization( hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)); return mInjector.binderWithCleanCallingIdentity(() -> { - List<OwnerDto> owners = mOwners.listAllOwners(); + SparseArray<DevicePolicyData> userData; + + // Gets the owners of "full users" first (device owner and profile owners) + List<OwnerShellData> owners = mOwners.listAllOwners(); synchronized (getLockObject()) { for (int i = 0; i < owners.size(); i++) { - OwnerDto owner = owners.get(i); + OwnerShellData owner = owners.get(i); owner.isAffiliated = isUserAffiliatedWithDeviceLocked(owner.userId); } + userData = mUserData; + } + + // Then the owners of profile users (managed profiles) + for (int i = 0; i < userData.size(); i++) { + DevicePolicyData policyData = mUserData.valueAt(i); + int userId = userData.keyAt(i); + int parentUserId = mUserManagerInternal.getProfileParentId(userId); + boolean isProfile = parentUserId != userId; + if (!isProfile) continue; + for (int j = 0; j < policyData.mAdminList.size(); j++) { + ActiveAdmin admin = policyData.mAdminList.get(j); + OwnerShellData owner = OwnerShellData.forManagedProfileOwner(userId, + parentUserId, admin.info.getComponent()); + owners.add(owner); + } } + return owners; }); } @@ -1962,13 +1997,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final DevicePolicyData policy = getUserData(UserHandle.getUserId(callerUid)); ActiveAdmin admin = policy.mAdminMap.get(adminComponent); - if (admin == null) { + // Throwing combined exception message for both the cases here, because from different + // security exceptions it could be deduced if particular package is admin package. + if (admin == null || admin.getUid() != callerUid) { throw new SecurityException(String.format( - "No active admin for %s", adminComponent)); - } - if (admin.getUid() != callerUid) { - throw new SecurityException(String.format( - "Admin %s is not owned by uid %d", adminComponent, callerUid)); + "Admin %s does not exist or is not owned by uid %d", adminComponent, + callerUid)); } if (callerPackage != null) { Preconditions.checkArgument(callerPackage.equals(adminComponent.getPackageName())); @@ -3764,7 +3798,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId"); - final CallerIdentity caller = getCallerIdentity(); + final CallerIdentity caller = hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS) + ? getCallerIdentity() : getCallerIdentity(adminReceiver); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REMOVE_ACTIVE_ADMIN); enforceUserUnlocked(userHandle); @@ -3781,8 +3816,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { + adminReceiver); return; } - Preconditions.checkCallAuthorization(admin.getUid() == caller.getUid() - || hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)); + mInjector.binderWithCleanCallingIdentity(() -> removeActiveAdminLocked(adminReceiver, userHandle)); } @@ -3817,9 +3851,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private boolean notSupportedOnAutomotive(String method) { + if (mIsAutomotive) { + Slogf.i(LOG_TAG, "%s is not supported on automotive builds", method); + return true; + } + return false; + } + @Override public void setPasswordQuality(ComponentName who, int quality, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || notSupportedOnAutomotive("setPasswordQuality")) { return; } Objects.requireNonNull(who, "ComponentName is null"); @@ -3961,6 +4003,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); + // System caller can query policy for a particular admin. + Preconditions.checkCallAuthorization( + who == null || isCallingFromPackage(who.getPackageName(), caller.getUid()) + || isSystemUid(caller)); synchronized (getLockObject()) { int mode = PASSWORD_QUALITY_UNSPECIFIED; @@ -4054,7 +4100,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setPasswordMinimumLength(ComponentName who, int length, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || notSupportedOnAutomotive("setPasswordMinimumLength")) { return; } Objects.requireNonNull(who, "ComponentName is null"); @@ -4176,7 +4222,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId"); - final CallerIdentity caller = getCallerIdentity(); + final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); synchronized (getLockObject()) { @@ -4326,7 +4372,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId"); - final CallerIdentity caller = getCallerIdentity(); + final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); synchronized (getLockObject()) { @@ -4336,7 +4382,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setPasswordMinimumUpperCase(ComponentName who, int length, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || notSupportedOnAutomotive("setPasswordMinimumUpperCase")) { return; } Objects.requireNonNull(who, "ComponentName is null"); @@ -4369,6 +4415,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setPasswordMinimumLowerCase(ComponentName who, int length, boolean parent) { + if (notSupportedOnAutomotive("setPasswordMinimumLowerCase")) { + return; + } Objects.requireNonNull(who, "ComponentName is null"); final int userId = mInjector.userHandleGetCallingUserId(); synchronized (getLockObject()) { @@ -4399,7 +4448,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setPasswordMinimumLetters(ComponentName who, int length, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || notSupportedOnAutomotive("setPasswordMinimumLetters")) { return; } Objects.requireNonNull(who, "ComponentName is null"); @@ -4431,7 +4480,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setPasswordMinimumNumeric(ComponentName who, int length, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || notSupportedOnAutomotive("setPasswordMinimumNumeric")) { return; } Objects.requireNonNull(who, "ComponentName is null"); @@ -4463,7 +4512,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setPasswordMinimumSymbols(ComponentName who, int length, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || notSupportedOnAutomotive("setPasswordMinimumSymbols")) { return; } Objects.requireNonNull(who, "ComponentName is null"); @@ -4495,7 +4544,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setPasswordMinimumNonLetter(ComponentName who, int length, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || notSupportedOnAutomotive("setPasswordMinimumNonLetter")) { return; } Objects.requireNonNull(who, "ComponentName is null"); @@ -4536,7 +4585,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId"); - final CallerIdentity caller = getCallerIdentity(); + final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); synchronized (getLockObject()) { @@ -4954,6 +5003,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); + // System caller can query policy for a particular admin. + Preconditions.checkCallAuthorization( + who == null || isCallingFromPackage(who.getPackageName(), caller.getUid()) + || isSystemUid(caller)); synchronized (getLockObject()) { ActiveAdmin admin = (who != null) @@ -5265,6 +5318,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); + // System caller can query policy for a particular admin. + Preconditions.checkCallAuthorization( + who == null || isCallingFromPackage(who.getPackageName(), caller.getUid()) + || isSystemUid(caller)); synchronized (getLockObject()) { if (who != null) { @@ -5342,7 +5399,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Preconditions.checkArgumentNonnegative(userId, "Invalid userId"); - final CallerIdentity caller = getCallerIdentity(); + final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userId)); if (!mLockPatternUtils.hasSecureLockScreen()) { @@ -7407,7 +7464,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId"); final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); + Preconditions.checkCallAuthorization( + hasFullCrossUsersPermission(caller, userHandle) && isSystemUid(caller)); synchronized (getLockObject()) { DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM); @@ -7685,6 +7743,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return false; } + + final CallerIdentity caller = getCallerIdentity(who); + Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); + if (parent) { Preconditions.checkCallAuthorization( isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity().getUserId())); @@ -8137,17 +8199,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ @Override public boolean getCameraDisabled(ComponentName who, int userHandle, boolean parent) { - return getCameraDisabled(who, userHandle, /* mergeDeviceOwnerRestriction= */ true, parent); - } - - private boolean getCameraDisabled(ComponentName who, int userHandle, - boolean mergeDeviceOwnerRestriction, boolean parent) { if (!mHasFeature) { return false; } + + final CallerIdentity caller = getCallerIdentity(who); + Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); + if (parent) { Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity().getUserId())); + isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId())); } synchronized (getLockObject()) { @@ -8156,17 +8217,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return (admin != null) && admin.disableCamera; } // First, see if DO has set it. If so, it's device-wide. - if (mergeDeviceOwnerRestriction) { - final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); - if (deviceOwner != null && deviceOwner.disableCamera) { - return true; - } + final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + if (deviceOwner != null && deviceOwner.disableCamera) { + return true; } final int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; // Return the strictest policy across all participating admins. List<ActiveAdmin> admins = getActiveAdminsForAffectedUserLocked(affectedUserId); // Determine whether or not the device camera is disabled for any active admins. - for (ActiveAdmin admin: admins) { + for (ActiveAdmin admin : admins) { if (admin.disableCamera) { return true; } @@ -8230,6 +8289,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); + Preconditions.checkCallAuthorization( + who == null || isCallingFromPackage(who.getPackageName(), caller.getUid()) + || isSystemUid(caller)); final long ident = mInjector.binderClearCallingIdentity(); try { @@ -8337,7 +8399,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public boolean setDeviceOwner(ComponentName admin, String ownerName, int userId) { + public boolean setDeviceOwner(ComponentName admin, String ownerName, int userId, + boolean setProfileOwnerOnCurrentUserIfNecessary) { if (!mHasFeature) { logMissingFeatureAction("Cannot set " + ComponentName.flattenToShortString(admin) + " as device owner for user " + userId); @@ -8400,7 +8463,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Slogf.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId); - if (mInjector.userManagerIsHeadlessSystemUserMode()) { + if (setProfileOwnerOnCurrentUserIfNecessary + && mInjector.userManagerIsHeadlessSystemUserMode()) { int currentForegroundUser = getCurrentForegroundUserId(); Slogf.i(LOG_TAG, "setDeviceOwner(): setting " + admin + " as profile owner on user " + currentForegroundUser); @@ -9128,9 +9192,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } /** - * Returns the ActiveAdmin associated wit the PO or DO on the given user. - * @param userHandle - * @return + * Returns the ActiveAdmin associated with the PO or DO on the given user. */ private @Nullable ActiveAdmin getDeviceOrProfileOwnerAdminLocked(int userHandle) { ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle); @@ -9537,7 +9599,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private @UserIdInt int getCurrentForegroundUserId() { try { - return mInjector.getIActivityManager().getCurrentUser().id; + UserInfo currentUser = mInjector.getIActivityManager().getCurrentUser(); + if (currentUser == null) { + // TODO(b/206107460): should not happen on production, but it's happening on unit + // tests that are not properly setting the expectation (because they don't need it) + Slogf.wtf(LOG_TAG, "getCurrentForegroundUserId(): mInjector.getIActivityManager()" + + ".getCurrentUser() returned null, please ignore when running unit tests"); + return ActivityManager.getCurrentUser(); + } + return currentUser.id; } catch (RemoteException e) { Slogf.wtf(LOG_TAG, "cannot get current user"); } @@ -9654,6 +9724,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mStatLogger.dump(pw); pw.println(); pw.println("Encryption Status: " + getEncryptionStatusName(getEncryptionStatus())); + pw.println("Logout user: " + getLogoutUserIdUnchecked()); pw.println(); if (mPendingUserCreatedCallbackTokens.isEmpty()) { @@ -9670,10 +9741,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mStateCache.dump(pw); pw.println(); } + mHandler.post(() -> handleDump(pw)); dumpResources(pw); } } + // Dump state that is guarded by the handler + private void handleDump(IndentingPrintWriter pw) { + if (mNetworkLoggingNotificationUserId != UserHandle.USER_NULL) { + pw.println("mNetworkLoggingNotificationUserId: " + mNetworkLoggingNotificationUserId); + } + } + private void dumpImmutableState(IndentingPrintWriter pw) { pw.println("Immutable state:"); pw.increaseIndent(); @@ -9888,7 +9967,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(agent, "agent null"); Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId"); - final CallerIdentity caller = getCallerIdentity(); + final CallerIdentity caller = getCallerIdentity(admin); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); synchronized (getLockObject()) { @@ -10613,19 +10692,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } final String adminPkg = admin.getPackageName(); - try { - // Install the profile owner if not present. - if (!mIPackageManager.isPackageAvailable(adminPkg, userId)) { - mIPackageManager.installExistingPackageAsUser(adminPkg, userId, - PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS, - PackageManager.INSTALL_REASON_POLICY, - /* allowlistedRestrictedPermissions= */ null); + mInjector.binderWithCleanCallingIdentity(() -> { + try { + // Install the profile owner if not present. + if (!mIPackageManager.isPackageAvailable(adminPkg, userId)) { + mIPackageManager.installExistingPackageAsUser(adminPkg, userId, + PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS, + PackageManager.INSTALL_REASON_POLICY, + /* allowlistedRestrictedPermissions= */ null); + } + } catch (RemoteException e) { + // Does not happen, same process + Slogf.wtf(LOG_TAG, e, "Failed to install admin package %s for user %d", + adminPkg, userId); } - } catch (RemoteException e) { - // Does not happen, same process - Slogf.wtf(LOG_TAG, e, "Failed to install admin package %s for user %d", - adminPkg, userId); - } + }); // Set admin. setActiveAdmin(profileOwner, /* refreshing= */ true, userId); @@ -10664,7 +10745,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - if (!mOwners.hasDeviceOwner() || !user.isFull() || user.isManagedProfile()) return; + if (!mOwners.hasDeviceOwner() || !user.isFull() || user.isManagedProfile() + || user.isGuest()) { + return; + } if (mInjector.userManagerIsHeadlessSystemUserMode()) { ComponentName admin = mOwners.getDeviceOwnerComponent(); @@ -10758,6 +10842,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkCallAuthorization(isDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SWITCH_USER); + boolean switched = false; + // Save previous logout user id in case of failure + int logoutUserId = getLogoutUserIdUnchecked(); synchronized (getLockObject()) { long id = mInjector.binderClearCallingIdentity(); try { @@ -10765,17 +10852,72 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (userHandle != null) { userId = userHandle.getIdentifier(); } - return mInjector.getIActivityManager().switchUser(userId); + Slogf.i(LOG_TAG, "Switching to user %d (logout user is %d)", userId, logoutUserId); + setLogoutUserIdLocked(UserHandle.USER_CURRENT); + switched = mInjector.getIActivityManager().switchUser(userId); + if (!switched) { + Slogf.w(LOG_TAG, "Failed to switch to user %d", userId); + } + return switched; } catch (RemoteException e) { Slogf.e(LOG_TAG, "Couldn't switch user", e); return false; } finally { mInjector.binderRestoreCallingIdentity(id); + if (!switched) { + setLogoutUserIdLocked(logoutUserId); + } } } } @Override + public int getLogoutUserId() { + Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())); + + return getLogoutUserIdUnchecked(); + } + + private @UserIdInt int getLogoutUserIdUnchecked() { + if (!mInjector.userManagerIsHeadlessSystemUserMode()) { + // mLogoutUserId is USER_SYSTEM as well, but there's no need to acquire the lock + return UserHandle.USER_SYSTEM; + } + synchronized (getLockObject()) { + return mLogoutUserId; + } + } + + @Override + public void clearLogoutUser() { + CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization(canManageUsers(caller)); + + Slogf.i(LOG_TAG, "Clearing logout user as requested by %s", caller); + clearLogoutUserUnchecked(); + } + + private void clearLogoutUserUnchecked() { + if (!mInjector.userManagerIsHeadlessSystemUserMode()) return; // ignore + + synchronized (getLockObject()) { + setLogoutUserIdLocked(UserHandle.USER_NULL); + } + } + + @GuardedBy("getLockObject()") + private void setLogoutUserIdLocked(@UserIdInt int userId) { + if (!mInjector.userManagerIsHeadlessSystemUserMode()) return; // ignore + + if (userId == UserHandle.USER_CURRENT) { + userId = getCurrentForegroundUserId(); + } + + Slogf.d(LOG_TAG, "setLogoutUserId(): %d -> %d", mLogoutUserId, userId); + mLogoutUserId = userId; + } + + @Override public int startUserInBackground(ComponentName who, UserHandle userHandle) { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(userHandle, "UserHandle is null"); @@ -10796,10 +10938,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS; } + Slogf.i(LOG_TAG, "Starting user %d in background", userId); if (mInjector.getIActivityManager().startUserInBackground(userId)) { - Slogf.i(LOG_TAG, "Started used %d in background", userId); return UserManager.USER_OPERATION_SUCCESS; } else { + Slogf.w(LOG_TAG, "failed to start user %d in background", userId); return UserManager.USER_OPERATION_ERROR_UNKNOWN; } } catch (RemoteException e) { @@ -10847,13 +10990,30 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE; } + // TODO(b/204585343): remove the headless system user check? + if (mInjector.userManagerIsHeadlessSystemUserMode() && callingUserId != mInjector + .binderWithCleanCallingIdentity(() -> getCurrentForegroundUserId())) { + Slogf.d(LOG_TAG, "logoutUser(): user %d is in background, just stopping, not switching", + callingUserId); + return stopUserUnchecked(callingUserId); + } + + int logoutUserId = getLogoutUserIdUnchecked(); + if (logoutUserId == UserHandle.USER_NULL) { + // Could happen on devices using headless system user mode when called before calling + // switchUser() or startUserInBackground() first + Slogf.w(LOG_TAG, "logoutUser(): could not determine which user to switch to"); + return UserManager.USER_OPERATION_ERROR_UNKNOWN; + } final long id = mInjector.binderClearCallingIdentity(); try { - if (!mInjector.getIActivityManager().switchUser(UserHandle.USER_SYSTEM)) { - Slogf.w(LOG_TAG, "Failed to switch to primary user"); - // This should never happen as target user is UserHandle.USER_SYSTEM + Slogf.i(LOG_TAG, "logoutUser(): switching to user %d", logoutUserId); + if (!mInjector.getIActivityManager().switchUser(logoutUserId)) { + Slogf.w(LOG_TAG, "Failed to switch to user %d", logoutUserId); + // This should never happen as target user is determined by getPreviousUserId() return UserManager.USER_OPERATION_ERROR_UNKNOWN; } + clearLogoutUserUnchecked(); } catch (RemoteException e) { // Same process, should not happen. return UserManager.USER_OPERATION_ERROR_UNKNOWN; @@ -10864,7 +11024,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return stopUserUnchecked(callingUserId); } - private int stopUserUnchecked(int userId) { + private int stopUserUnchecked(@UserIdInt int userId) { + Slogf.i(LOG_TAG, "Stopping user %d", userId); final long id = mInjector.binderClearCallingIdentity(); try { switch (mInjector.getIActivityManager().stopUser(userId, true /*force*/, null)) { @@ -13083,14 +13244,29 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { try { List<UserManager.EnforcingUser> sources = mUserManager .getUserRestrictionSources(restriction, UserHandle.of(userId)); - if (sources == null || sources.isEmpty()) { + if (sources == null) { // The restriction is not enforced. return null; - } else if (sources.size() > 1) { + } + int sizeBefore = sources.size(); + if (sizeBefore > 1) { + Slogf.d(LOG_TAG, "getEnforcingAdminAndUserDetailsInternal(%d, %s): " + + "%d sources found, excluding those set by UserManager", + userId, restriction, sizeBefore); + sources = getDevicePolicySources(sources); + } + if (sources.isEmpty()) { + // The restriction is not enforced (or is just enforced by the system) + return null; + } + + if (sources.size() > 1) { // In this case, we'll show an admin support dialog that does not // specify the admin. // TODO(b/128928355): if this restriction is enforced by multiple DPCs, return // the admin for the calling user. + Slogf.w(LOG_TAG, "getEnforcingAdminAndUserDetailsInternal(%d, %s): multiple " + + "sources for restriction %s on user %d", restriction, userId); result = new Bundle(); result.putInt(Intent.EXTRA_USER_ID, userId); return result; @@ -13136,6 +13312,32 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } /** + * Excludes restrictions imposed by UserManager. + */ + private List<UserManager.EnforcingUser> getDevicePolicySources( + List<UserManager.EnforcingUser> sources) { + int sizeBefore = sources.size(); + List<UserManager.EnforcingUser> realSources = new ArrayList<>(sizeBefore); + for (int i = 0; i < sizeBefore; i++) { + UserManager.EnforcingUser source = sources.get(i); + int type = source.getUserRestrictionSource(); + if (type != UserManager.RESTRICTION_SOURCE_PROFILE_OWNER + && type != UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) { + // TODO(b/128928355): add unit test + Slogf.d(LOG_TAG, "excluding source of type %s at index %d", + userRestrictionSourceToString(type), i); + continue; + } + realSources.add(source); + } + return realSources; + } + + private static String userRestrictionSourceToString(@UserRestrictionSource int source) { + return DebugUtils.flagsToString(UserManager.class, "RESTRICTION_", source); + } + + /** * @param restriction The restriction enforced by admin. It could be any user restriction or * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}. @@ -14344,6 +14546,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { maybePauseDeviceWideLoggingLocked(); maybeResumeDeviceWideLoggingLocked(); maybeClearLockTaskPolicyLocked(); + updateAdminCanGrantSensorsPermissionCache(callingUserId); } } @@ -15077,11 +15280,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } if (active) { if (shouldSendNotification) { - mHandler.post(() -> sendNetworkLoggingNotification()); + mHandler.post(() -> handleSendNetworkLoggingNotification()); } } else { - mHandler.post(() -> mInjector.getNotificationManager().cancel( - SystemMessage.NOTE_NETWORK_LOGGING)); + mHandler.post(() -> handleCancelNetworkLoggingNotification()); } }); } @@ -15272,10 +15474,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return true; } - private void sendNetworkLoggingNotification() { + private void handleSendNetworkLoggingNotification() { final PackageManagerInternal pm = mInjector.getPackageManagerInternal(); final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG); intent.setPackage(pm.getSystemUiServiceComponent().getPackageName()); + mNetworkLoggingNotificationUserId = getCurrentForegroundUserId(); // Simple notification clicks are immutable final PendingIntent pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT); @@ -15290,7 +15493,26 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .setStyle(new Notification.BigTextStyle() .bigText(mContext.getString(R.string.network_logging_notification_text))) .build(); - mInjector.getNotificationManager().notify(SystemMessage.NOTE_NETWORK_LOGGING, notification); + Slogf.i(LOG_TAG, "Sending network logging notification to user %d", + mNetworkLoggingNotificationUserId); + mInjector.getNotificationManager().notifyAsUser(/* tag= */ null, + SystemMessage.NOTE_NETWORK_LOGGING, notification, + UserHandle.of(mNetworkLoggingNotificationUserId)); + } + + private void handleCancelNetworkLoggingNotification() { + if (mNetworkLoggingNotificationUserId == UserHandle.USER_NULL) { + // Happens when setNetworkLoggingActive(false) is called before called with true + Slogf.d(LOG_TAG, "Not cancelling network logging notification for USER_NULL"); + return; + } + + Slogf.i(LOG_TAG, "Cancelling network logging notification for user %d", + mNetworkLoggingNotificationUserId); + mInjector.getNotificationManager().cancelAsUser(/* tag= */ null, + SystemMessage.NOTE_NETWORK_LOGGING, + UserHandle.of(mNetworkLoggingNotificationUserId)); + mNetworkLoggingNotificationUserId = UserHandle.USER_NULL; } /** @@ -17007,6 +17229,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public void clearOrganizationIdForUser(int userHandle) { + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + + synchronized (getLockObject()) { + final ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userHandle); + owner.mOrganizationId = null; + owner.mEnrollmentSpecificId = null; + saveSettingsLocked(userHandle); + } + } + + @Override public UserHandle createAndProvisionManagedProfile( @NonNull ManagedProfileProvisioningParams provisioningParams, @NonNull String callerPackage) { @@ -17453,7 +17688,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // TODO(b/178187130): Directly set DO and remove the check once silent provisioning is no // longer used. if (getDeviceOwnerComponent(/* callingUserOnly= */ true) == null) { - return setDeviceOwner(adminComponent, name, userId); + return setDeviceOwner(adminComponent, name, userId, + /* setProfileOwnerOnCurrentUserIfNecessary= */ true); } return true; } @@ -17508,7 +17744,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { }); } - private void setAdminCanGrantSensorsPermissionForUserUnchecked(int userId, boolean canGrant) { + private void setAdminCanGrantSensorsPermissionForUserUnchecked(@UserIdInt int userId, + boolean canGrant) { + Slogf.d(LOG_TAG, "setAdminCanGrantSensorsPermissionForUserUnchecked(%d, %b)", + userId, canGrant); synchronized (getLockObject()) { ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId); @@ -17522,10 +17761,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void updateAdminCanGrantSensorsPermissionCache(int userId) { + private void updateAdminCanGrantSensorsPermissionCache(@UserIdInt int userId) { synchronized (getLockObject()) { - ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId); - final boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false; + + ActiveAdmin owner; + // If the user is affiliated the device (either a DO itself, or an affiliated PO), + // use mAdminCanGrantSensorsPermissions from the DO + if (isUserAffiliatedWithDeviceLocked(userId)) { + owner = getDeviceOwnerAdminLocked(); + } else { + owner = getDeviceOrProfileOwnerAdminLocked(userId); + } + boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false; mPolicyCache.setAdminCanGrantSensorsPermissions(userId, canGrant); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java index a2db6aaca3df..e1d720ca25c8 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java @@ -22,8 +22,6 @@ import android.os.ShellCommand; import android.os.SystemClock; import android.os.UserHandle; -import com.android.server.devicepolicy.Owners.OwnerDto; - import java.io.PrintWriter; import java.util.Collection; import java.util.List; @@ -48,11 +46,13 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { private static final String USER_OPTION = "--user"; private static final String NAME_OPTION = "--name"; + private static final String DO_ONLY_OPTION = "--device-owner-only"; private final DevicePolicyManagerService mService; private int mUserId = UserHandle.USER_SYSTEM; private String mName = ""; private ComponentName mComponent; + private boolean mSetDoOnly; DevicePolicyManagerServiceShellCommand(DevicePolicyManagerService service) { mService = Objects.requireNonNull(service); @@ -132,8 +132,8 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { pw.printf(" %s [ %s <USER_ID> | current ] <COMPONENT>\n", CMD_SET_ACTIVE_ADMIN, USER_OPTION); pw.printf(" Sets the given component as active admin for an existing user.\n\n"); - pw.printf(" %s [ %s <USER_ID> | current *EXPERIMENTAL* ] [ %s <NAME> ] " - + "<COMPONENT>\n", CMD_SET_DEVICE_OWNER, USER_OPTION, NAME_OPTION); + pw.printf(" %s [ %s <USER_ID> | current *EXPERIMENTAL* ] [ %s <NAME> ] [ %s ]" + + "<COMPONENT>\n", CMD_SET_DEVICE_OWNER, USER_OPTION, NAME_OPTION, DO_ONLY_OPTION); pw.printf(" Sets the given component as active admin, and its package as device owner." + "\n\n"); pw.printf(" %s [ %s <USER_ID> | current ] [ %s <NAME> ] <COMPONENT>\n", @@ -205,12 +205,12 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { } private int runListOwners(PrintWriter pw) { - List<OwnerDto> owners = mService.listAllOwners(); + List<OwnerShellData> owners = mService.listAllOwners(); int size = printAndGetSize(pw, owners, "owner"); if (size == 0) return 0; for (int i = 0; i < size; i++) { - OwnerDto owner = owners.get(i); + OwnerShellData owner = owners.get(i); pw.printf("User %2d: admin=%s", owner.userId, owner.admin.flattenToShortString()); if (owner.isDeviceOwner) { pw.print(",DeviceOwner"); @@ -218,6 +218,9 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { if (owner.isProfileOwner) { pw.print(",ProfileOwner"); } + if (owner.isManagedProfileOwner) { + pw.printf(",ManagedProfileOwner(parentUserId=%d)", owner.parentUserId); + } if (owner.isAffiliated) { pw.print(",Affiliated"); } @@ -253,7 +256,8 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId); try { - if (!mService.setDeviceOwner(mComponent, mName, mUserId)) { + if (!mService.setDeviceOwner(mComponent, mName, mUserId, + /* setProfileOwnerOnCurrentUserIfNecessary= */ !mSetDoOnly)) { throw new RuntimeException( "Can't set package " + mComponent + " as device owner."); } @@ -350,6 +354,8 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { if (mUserId == UserHandle.USER_CURRENT) { mUserId = ActivityManager.getCurrentUser(); } + } else if (DO_ONLY_OPTION.equals(opt)) { + mSetDoOnly = true; } else if (canHaveName && NAME_OPTION.equals(opt)) { mName = getNextArgRequired(); } else { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnerShellData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnerShellData.java new file mode 100644 index 000000000000..b98c3dc2ac07 --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnerShellData.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 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.devicepolicy; + +import static android.os.UserHandle.USER_NULL; + +import android.annotation.UserIdInt; +import android.content.ComponentName; + +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * Data-transfer object used by {@link DevicePolicyManagerServiceShellCommand}. + */ +final class OwnerShellData { + + public final @UserIdInt int userId; + public final @UserIdInt int parentUserId; + public final ComponentName admin; + public final boolean isDeviceOwner; + public final boolean isProfileOwner; + public final boolean isManagedProfileOwner; + public boolean isAffiliated; + + // NOTE: class is too simple to require a Builder (not to mention isAffiliated is mutable) + private OwnerShellData(@UserIdInt int userId, @UserIdInt int parentUserId, ComponentName admin, + boolean isDeviceOwner, boolean isProfileOwner, boolean isManagedProfileOwner) { + Preconditions.checkArgument(userId != USER_NULL, "userId cannot be USER_NULL"); + this.userId = userId; + this.parentUserId = parentUserId; + this.admin = Objects.requireNonNull(admin, "admin must not be null"); + this.isDeviceOwner = isDeviceOwner; + this.isProfileOwner = isProfileOwner; + this.isManagedProfileOwner = isManagedProfileOwner; + if (isManagedProfileOwner) { + Preconditions.checkArgument(parentUserId != USER_NULL, + "parentUserId cannot be USER_NULL for managed profile owner"); + Preconditions.checkArgument(parentUserId != userId, + "cannot be parent of itself (%d)", userId); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(getClass().getSimpleName()) + .append("[userId=").append(userId) + .append(",admin=").append(admin.flattenToShortString()); + if (isDeviceOwner) { + sb.append(",deviceOwner"); + } + if (isProfileOwner) { + sb.append(",isProfileOwner"); + } + if (isManagedProfileOwner) { + sb.append(",isManagedProfileOwner"); + } + if (parentUserId != USER_NULL) { + sb.append(",parentUserId=").append(parentUserId); + } + if (isAffiliated) { + sb.append(",isAffiliated"); + } + return sb.append(']').toString(); + } + + static OwnerShellData forDeviceOwner(@UserIdInt int userId, ComponentName admin) { + return new OwnerShellData(userId, /* parentUserId= */ USER_NULL, admin, + /* isDeviceOwner= */ true, /* isProfileOwner= */ false, + /* isManagedProfileOwner= */ false); + } + + static OwnerShellData forUserProfileOwner(@UserIdInt int userId, ComponentName admin) { + return new OwnerShellData(userId, /* parentUserId= */ USER_NULL, admin, + /* isDeviceOwner= */ false, /* isProfileOwner= */ true, + /* isManagedProfileOwner= */ false); + } + + static OwnerShellData forManagedProfileOwner(@UserIdInt int userId, @UserIdInt int parentUserId, + ComponentName admin) { + return new OwnerShellData(userId, parentUserId, admin, /* isDeviceOwner= */ false, + /* isProfileOwner= */ false, /* isManagedProfileOwner= */ true); + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index fd09e3f9cfd0..3584728a2e62 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -19,7 +19,6 @@ package com.android.server.devicepolicy; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; import android.annotation.Nullable; -import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; import android.app.AppOpsManagerInternal; import android.app.admin.DevicePolicyManager.DeviceOwnerType; @@ -476,17 +475,16 @@ class Owners { } } - List<OwnerDto> listAllOwners() { - List<OwnerDto> owners = new ArrayList<>(); + List<OwnerShellData> listAllOwners() { + List<OwnerShellData> owners = new ArrayList<>(); synchronized (mLock) { if (mDeviceOwner != null) { - owners.add(new OwnerDto(mDeviceOwnerUserId, mDeviceOwner.admin, - /* isDeviceOwner= */ true)); + owners.add(OwnerShellData.forDeviceOwner(mDeviceOwnerUserId, mDeviceOwner.admin)); } for (int i = 0; i < mProfileOwners.size(); i++) { int userId = mProfileOwners.keyAt(i); OwnerInfo info = mProfileOwners.valueAt(i); - owners.add(new OwnerDto(userId, info.admin, /* isDeviceOwner= */ false)); + owners.add(OwnerShellData.forUserProfileOwner(userId, info.admin)); } } return owners; @@ -1236,24 +1234,6 @@ class Owners { } } - /** - * Data-transfer object used by {@link DevicePolicyManagerServiceShellCommand}. - */ - static final class OwnerDto { - public final @UserIdInt int userId; - public final ComponentName admin; - public final boolean isDeviceOwner; - public final boolean isProfileOwner; - public boolean isAffiliated; - - private OwnerDto(@UserIdInt int userId, ComponentName admin, boolean isDeviceOwner) { - this.userId = userId; - this.admin = Objects.requireNonNull(admin, "admin must not be null"); - this.isDeviceOwner = isDeviceOwner; - this.isProfileOwner = !isDeviceOwner; - } - } - public void dump(IndentingPrintWriter pw) { boolean needBlank = false; if (mDeviceOwner != null) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index db6323c4100f..97e1f0b2df99 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -381,6 +381,8 @@ public final class SystemServer implements Dumpable { "com.android.server.connectivity.IpConnectivityMetrics"; private static final String MEDIA_COMMUNICATION_SERVICE_CLASS = "com.android.server.media.MediaCommunicationService"; + private static final String APP_COMPAT_OVERRIDES_SERVICE_CLASS = + "com.android.server.compat.overrides.AppCompatOverridesService$Lifecycle"; private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService"; private static final String GAME_MANAGER_SERVICE_CLASS = @@ -1592,6 +1594,9 @@ public final class SystemServer implements Dumpable { // all listeners have the chance to react with special handling. Settings.Global.putInt(context.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1); + } else if (context.getResources().getBoolean(R.bool.config_autoResetAirplaneMode)) { + Settings.Global.putInt(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0); } StatusBarManagerService statusBar = null; @@ -2702,6 +2707,10 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(MEDIA_COMMUNICATION_SERVICE_CLASS); t.traceEnd(); + t.traceBegin("AppCompatOverridesService"); + mSystemServiceManager.startService(APP_COMPAT_OVERRIDES_SERVICE_CLASS); + t.traceEnd(); + // These are needed to propagate to the runnable below. final NetworkManagementService networkManagementF = networkManagement; final NetworkStatsService networkStatsF = networkStats; diff --git a/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java b/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java index 49d5e50e0345..d3353cd6adc7 100644 --- a/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java +++ b/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java @@ -31,6 +31,7 @@ import android.os.CancellationSignal; import com.android.server.LocalServices; import com.android.server.people.PeopleServiceInternal; +import com.android.server.pm.PackageManagerService; /** * If a {@link ConversationStatus} is added to the system with an expiration time, remove that @@ -50,6 +51,7 @@ public class ConversationStatusExpirationBroadcastReceiver extends BroadcastRece final PendingIntent pi = PendingIntent.getBroadcast(context, REQUEST_CODE, new Intent(ACTION) + .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME) .setData(new Uri.Builder().scheme(SCHEME) .appendPath(getKey(userId, pkg, conversationId, status)) .build()) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index 886b2e03a104..347952bcebb0 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -19,6 +19,7 @@ package com.android.server.pm.test.verify.domain import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.content.pm.PackageParser.SigningDetails import android.content.pm.PackageUserState import android.content.pm.parsing.component.ParsedActivity import android.content.pm.parsing.component.ParsedIntentInfo @@ -27,6 +28,7 @@ import android.content.pm.verify.domain.DomainVerificationState import android.os.Build import android.os.Process import android.util.ArraySet +import android.util.IndentingPrintWriter import android.util.SparseArray import androidx.test.platform.app.InstrumentationRegistry import com.android.server.pm.PackageSetting @@ -47,6 +49,7 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyLong import org.mockito.Mockito.anyString import org.mockito.Mockito.eq +import org.mockito.Mockito.mock import org.mockito.Mockito.verifyNoMoreInteractions import java.io.File import java.util.UUID @@ -206,6 +209,14 @@ class DomainVerificationEnforcerTest { service(Type.QUERENT, "getInfo") { getDomainVerificationInfo(it.targetPackageName) }, + service(Type.QUERENT, "printState") { + printState(mock(IndentingPrintWriter::class.java), null, null) + }, + service(Type.QUERENT, "printStateInternal") { + printState(mock(IndentingPrintWriter::class.java), null, null) { + mockPkgSetting(it, UUID.randomUUID()) + } + }, service(Type.VERIFIER, "setStatus") { setDomainVerificationStatus( it.targetDomainSetId, @@ -311,6 +322,7 @@ class DomainVerificationEnforcerTest { } ) } + whenever(signingDetails) { SigningDetails.UNKNOWN } } fun mockPkgSetting(packageName: String, domainSetId: UUID) = spyThrowOnUnmocked( @@ -339,6 +351,7 @@ class DomainVerificationEnforcerTest { whenever(readUserState(1)) { PackageUserState() } whenever(getInstantApp(anyInt())) { false } whenever(isSystem()) { false } + whenever(signingDetails) { SigningDetails.UNKNOWN } } } @@ -385,6 +398,7 @@ class DomainVerificationEnforcerTest { val allowUserState = AtomicBoolean(false) val allowPreferredApps = AtomicBoolean(false) val allowQueryAll = AtomicBoolean(false) + val allowDump = AtomicBoolean(false) val context: Context = mockThrowOnUnmocked { initPermission( allowUserState, @@ -395,6 +409,7 @@ class DomainVerificationEnforcerTest { android.Manifest.permission.SET_PREFERRED_APPLICATIONS ) initPermission(allowQueryAll, android.Manifest.permission.QUERY_ALL_PACKAGES) + initPermission(allowDump, android.Manifest.permission.DUMP) } val target = params.construct(context) @@ -421,6 +436,10 @@ class DomainVerificationEnforcerTest { allowQueryAll.set(true) assertFails { runMethod(target, NON_VERIFIER_UID) } + + allowDump.set(true) + + runMethod(target, NON_VERIFIER_UID) } private fun approvedVerifier() { @@ -806,8 +825,12 @@ class DomainVerificationEnforcerTest { } val valueAsInt = value as? Int - if (valueAsInt != null && valueAsInt == DomainVerificationManager.STATUS_OK) { - throw AssertionError("Expected call to return false, was $value") + if (valueAsInt != null) { + if (valueAsInt == DomainVerificationManager.STATUS_OK) { + throw AssertionError("Expected call to return false, was $value") + } + } else { + throw AssertionError("Expected call to fail") } } catch (e: SecurityException) { } catch (e: PackageManager.NameNotFoundException) { @@ -819,7 +842,7 @@ class DomainVerificationEnforcerTest { // System/shell only INTERNAL, - // INTERNAL || non-legacy domain verification agent + // INTERNAL || non-legacy domain verification agent || DUMP permission QUERENT, // INTERNAL || domain verification agent diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt index 6c2a8916617b..19eb456a9a95 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt @@ -618,6 +618,60 @@ class DomainVerificationPackageTest { } @Test + fun migratePackageSelected() { + val pkgName = PKG_ONE + val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE, + listOf(DOMAIN_1), listOf(DOMAIN_2)) + val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO, + listOf(DOMAIN_1), listOf(DOMAIN_2)) + + val map = mutableMapOf<String, PackageSetting>() + val service = makeService { map[it] } + service.addPackage(pkgBefore) + + // Only insert the package after addPackage call to ensure the service doesn't access + // a live package inside the addPackage logic. It should only use the provided input. + map[pkgName] = pkgBefore + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS)) + .isEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID)) + .isEqualTo(DomainVerificationManager.STATUS_OK) + + service.getInfo(pkgName).run { + assertThat(identifier).isEqualTo(UUID_ONE) + assertThat(hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_SUCCESS, + )) + } + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + )) + assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) + + // Now remove the package because migrateState shouldn't use it either + map.remove(pkgName) + + service.migrateState(pkgBefore, pkgAfter) + + map[pkgName] = pkgAfter + + service.getInfo(pkgName).run { + assertThat(identifier).isEqualTo(UUID_TWO) + assertThat(hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_SUCCESS, + )) + } + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + )) + assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) + } + + @Test fun backupAndRestore() { // This test acts as a proxy for true user restore through PackageManager, // as that's much harder to test for real. @@ -798,7 +852,8 @@ class DomainVerificationPackageTest { pkgName: String, domainSetId: UUID, signature: String, - domains: List<String> = listOf(DOMAIN_1, DOMAIN_2), + autoVerifyDomains: List<String> = listOf(DOMAIN_1, DOMAIN_2), + otherDomains: List<String> = listOf(), isSystemApp: Boolean = false ) = mockThrowOnUnmocked<PackageSetting> { val pkg = mockThrowOnUnmocked<AndroidPackage> { @@ -806,21 +861,23 @@ class DomainVerificationPackageTest { whenever(targetSdkVersion) { Build.VERSION_CODES.S } whenever(isEnabled) { true } + fun baseIntent(domain: String) = ParsedIntentInfo().apply { + addAction(Intent.ACTION_VIEW) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme("http") + addDataScheme("https") + addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) + addDataAuthority(domain, null) + } + val activityList = listOf( ParsedActivity().apply { - domains.forEach { - addIntent( - ParsedIntentInfo().apply { - autoVerify = true - addAction(Intent.ACTION_VIEW) - addCategory(Intent.CATEGORY_BROWSABLE) - addCategory(Intent.CATEGORY_DEFAULT) - addDataScheme("http") - addDataScheme("https") - addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) - addDataAuthority(it, null) - } - ) + autoVerifyDomains.forEach { + addIntent(baseIntent(it).apply { autoVerify = true }) + } + otherDomains.forEach { + addIntent(baseIntent(it).apply { autoVerify = false }) } }, ) diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 18f1267b890f..0dd4f5b338b4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL; import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; @@ -1854,6 +1855,36 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") @Test + public void testUpdateOomAdj_DoAll_BoundByPersService_Cycle_Branch_Capability() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); + ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, + MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + bindService(app, client, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class)); + ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, + MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); + bindService(client, client2, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class)); + bindService(client2, app, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class)); + ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID, + MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false)); + client3.mState.setMaxAdj(PERSISTENT_PROC_ADJ); + bindService(app, client3, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class)); + ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP(); + lru.clear(); + lru.add(app); + lru.add(client); + lru.add(client2); + lru.add(client3); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + + assertEquals(PROCESS_CAPABILITY_ALL, client.mState.getSetCapability()); + assertEquals(PROCESS_CAPABILITY_ALL, client2.mState.getSetCapability()); + assertEquals(PROCESS_CAPABILITY_ALL, app.mState.getSetCapability()); + } + + @SuppressWarnings("GuardedBy") + @Test public void testUpdateOomAdj_DoAll_Provider_Cycle_Branch_2() { ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/OWNERS b/services/tests/mockingservicestests/src/com/android/server/compat/OWNERS new file mode 100644 index 000000000000..f8c3520e9fa8 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/compat/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/compat/OWNERS diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesParserTest.java b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesParserTest.java new file mode 100644 index 000000000000..df19be4a9cfe --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesParserTest.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2021 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.compat.overrides; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +import static java.util.Collections.emptySet; + +import android.app.compat.PackageOverride; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Arrays; +import java.util.Map; +import java.util.Set; + +/** + * Test class for {@link AppCompatOverridesParser}. + * + * Build/Install/Run: + * atest FrameworksMockingServicesTests:AppCompatOverridesParserTest + */ +@RunWith(MockitoJUnitRunner.class) +@SmallTest +@Presubmit +public class AppCompatOverridesParserTest { + private static final String PACKAGE_1 = "com.android.test1"; + private static final String PACKAGE_2 = "com.android.test2"; + private static final String PACKAGE_3 = "com.android.test3"; + private static final String PACKAGE_4 = "com.android.test4"; + + private AppCompatOverridesParser mParser; + + @Mock + private PackageManager mPackageManager; + + @Before + public void setUp() throws Exception { + mParser = new AppCompatOverridesParser(mPackageManager); + } + + @Test + public void parseRemoveOverrides_emptyConfig_returnsEmpty() { + Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(123L, 456L)); + + assertThat(mParser.parseRemoveOverrides("", ownedChangeIds)).isEmpty(); + } + + @Test + public void parseRemoveOverrides_configHasWildcardNoOwnedChangeIds_returnsEmpty() { + when(mPackageManager.getInstalledApplications(anyInt())) + .thenReturn(Arrays.asList(createAppInfo(PACKAGE_1), createAppInfo(PACKAGE_2))); + + assertThat(mParser.parseRemoveOverrides("*", /* ownedChangeIds= */ emptySet())).isEmpty(); + } + + @Test + public void parseRemoveOverrides_configHasWildcard_returnsAllInstalledPackagesToAllOwnedIds() { + Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(123L, 456L)); + when(mPackageManager.getInstalledApplications(anyInt())) + .thenReturn(Arrays.asList(createAppInfo(PACKAGE_1), createAppInfo(PACKAGE_2), + createAppInfo(PACKAGE_3))); + + Map<String, Set<Long>> result = mParser.parseRemoveOverrides("*", ownedChangeIds); + + assertThat(result).hasSize(3); + assertThat(result.get(PACKAGE_1)).containsExactly(123L, 456L); + assertThat(result.get(PACKAGE_2)).containsExactly(123L, 456L); + assertThat(result.get(PACKAGE_3)).containsExactly(123L, 456L); + } + + @Test + public void parseRemoveOverrides_configHasInvalidWildcardSymbol_returnsEmpty() { + Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(123L, 456L)); + when(mPackageManager.getInstalledApplications(anyInt())).thenReturn( + Arrays.asList(createAppInfo(PACKAGE_1), createAppInfo(PACKAGE_2))); + + assertThat(mParser.parseRemoveOverrides("**", ownedChangeIds)).isEmpty(); + } + + @Test + public void parseRemoveOverrides_configHasSingleEntry_returnsPackageToChangeIds() { + Map<String, Set<Long>> result = mParser.parseRemoveOverrides( + PACKAGE_1 + "=12:34", /* ownedChangeIds= */ emptySet()); + + assertThat(result).hasSize(1); + assertThat(result.get(PACKAGE_1)).containsExactly(12L, 34L); + } + + @Test + public void parseRemoveOverrides_configHasMultipleEntries_returnsPackagesToChangeIds() { + Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(12L, 34L, 56L, 78L)); + + Map<String, Set<Long>> result = mParser.parseRemoveOverrides( + PACKAGE_1 + "=12," + PACKAGE_2 + "=*," + PACKAGE_3 + "=12:56:78," + PACKAGE_4 + + "=", ownedChangeIds); + + assertThat(result).hasSize(3); + assertThat(result.get(PACKAGE_1)).containsExactly(12L); + assertThat(result.get(PACKAGE_2)).containsExactly(12L, 34L, 56L, 78L); + assertThat(result.get(PACKAGE_3)).containsExactly(12L, 56L, 78L); + } + + @Test + public void parseRemoveOverrides_configHasPackageWithWildcardNoOwnedId_returnsWithoutPackage() { + Map<String, Set<Long>> result = mParser.parseRemoveOverrides( + PACKAGE_1 + "=*," + PACKAGE_2 + "=12", /* ownedChangeIds= */ emptySet()); + + assertThat(result).hasSize(1); + assertThat(result.get(PACKAGE_2)).containsExactly(12L); + } + + @Test + public void parseRemoveOverrides_configHasInvalidKeyValueListFormat_returnsEmpty() { + Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(12L, 34L)); + + assertThat(mParser.parseRemoveOverrides( + PACKAGE_1 + "=12," + PACKAGE_2 + ">34", ownedChangeIds)).isEmpty(); + } + + + @Test + public void parseRemoveOverrides_configHasInvalidChangeIds_returnsWithoutInvalidChangeIds() { + Map<String, Set<Long>> result = mParser.parseRemoveOverrides( + PACKAGE_1 + "=12," + PACKAGE_2 + "=12:56L:78," + PACKAGE_3 + + "=34L", /* ownedChangeIds= */ emptySet()); + + assertThat(result).hasSize(2); + assertThat(result.get(PACKAGE_1)).containsExactly(12L); + assertThat(result.get(PACKAGE_2)).containsExactly(12L, 78L); + } + + @Test + public void parseOwnedChangeIds_emptyConfig_returnsEmpty() { + assertThat(AppCompatOverridesParser.parseOwnedChangeIds("")).isEmpty(); + } + + @Test + public void parseOwnedChangeIds_configHasSingleChangeId_returnsChangeId() { + assertThat(AppCompatOverridesParser.parseOwnedChangeIds("123")).containsExactly(123L); + } + + @Test + public void parseOwnedChangeIds_configHasMultipleChangeIds_returnsChangeIds() { + assertThat(AppCompatOverridesParser.parseOwnedChangeIds("12,34,56")).containsExactly(12L, + 34L, 56L); + } + + @Test + public void parseOwnedChangeIds_configHasInvalidChangeIds_returnsWithoutInvalidChangeIds() { + // We add a valid entry before and after the invalid ones to make sure they are applied. + assertThat(AppCompatOverridesParser.parseOwnedChangeIds("12,C34,56")).containsExactly(12L, + 56L); + } + + @Test + public void parsePackageOverrides_emptyConfigNoOwnedChangeIds_returnsEmpty() { + Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides( + /* configStr= */ "", /* versionCode= */ 0, /* changeIdsToSkip= */ emptySet()); + + assertThat(result).isEmpty(); + } + + @Test + public void parsePackageOverrides_configWithSingleOverride_returnsOverride() { + Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides( + /* configStr= */ "123:::true", /* versionCode= */ 5, /* changeIdsToSkip= */ + emptySet()); + + assertThat(result).hasSize(1); + assertThat(result.get(123L)).isEqualTo( + new PackageOverride.Builder().setEnabled(true).build()); + } + + @Test + public void parsePackageOverrides_configWithMultipleOverrides_returnsOverrides() { + Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides( + /* configStr= */ "910:3:4:false,78:10::false,12:::false,34:1:2:true,34:10::true," + + "56::2:true,56:3:4:false,34:4:8:true,78:6:7:true,910:5::true," + + "1112::5:true,56:6::true,1112:6:7:false", /* versionCode= */ + 5, /* changeIdsToSkip= */ emptySet()); + + assertThat(result).hasSize(6); + assertThat(result.get(12L)).isEqualTo( + new PackageOverride.Builder().setEnabled(false).build()); + assertThat(result.get(34L)).isEqualTo( + new PackageOverride.Builder().setMinVersionCode(4).setMaxVersionCode(8).setEnabled( + true).build()); + assertThat(result.get(56L)).isEqualTo( + new PackageOverride.Builder().setMinVersionCode(3).setMaxVersionCode(4).setEnabled( + false).build()); + assertThat(result.get(78L)).isEqualTo( + new PackageOverride.Builder().setMinVersionCode(6).setMaxVersionCode(7).setEnabled( + true).build()); + assertThat(result.get(910L)).isEqualTo( + new PackageOverride.Builder().setMinVersionCode(5).setEnabled(true).build()); + assertThat(result.get(1112L)).isEqualTo( + new PackageOverride.Builder().setMaxVersionCode(5).setEnabled(true).build()); + } + + @Test + public void parsePackageOverrides_changeIdsToSkipSpecified_returnsWithoutChangeIdsToSkip() { + ArraySet<Long> changeIdsToSkip = new ArraySet<>(Arrays.asList(34L, 56L)); + Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides( + /* configStr= */ "12:::true,56:3:7:true", /* versionCode= */ 5, changeIdsToSkip); + + assertThat(result).hasSize(1); + assertThat(result.get(12L)).isEqualTo( + new PackageOverride.Builder().setEnabled(true).build()); + } + + @Test + public void parsePackageOverrides_changeIdsToSkipContainsAllIds_returnsEmpty() { + ArraySet<Long> changeIdsToSkip = new ArraySet<>(Arrays.asList(12L, 34L)); + Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides( + /* configStr= */ "12:::true", /* versionCode= */ 5, changeIdsToSkip); + + assertThat(result).isEmpty(); + } + + @Test + public void parsePackageOverrides_someOverridesAreInvalid_returnsWithoutInvalidOverrides() { + // We add a valid entry before and after the invalid ones to make sure they are applied. + Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides( + /* configStr= */ "12:::True,56:1:2:FALSE,56:3:true,78:4:8:true:,C1:::true,910:::no," + + "1112:1:ten:true,1112:one:10:true,,1314:7:3:false,34:::", + /* versionCode= */ 5, /* changeIdsToSkip= */ emptySet()); + + assertThat(result).hasSize(2); + assertThat(result.get(12L)).isEqualTo( + new PackageOverride.Builder().setEnabled(true).build()); + assertThat(result.get(56L)).isEqualTo( + new PackageOverride.Builder().setMinVersionCode(1).setMaxVersionCode(2).setEnabled( + false).build()); + } + + private static ApplicationInfo createAppInfo(String packageName) { + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.packageName = packageName; + return appInfo; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java new file mode 100644 index 000000000000..007191f02631 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java @@ -0,0 +1,726 @@ +/* + * Copyright (C) 2021 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.compat.overrides; + +import static android.content.Intent.ACTION_PACKAGE_ADDED; +import static android.content.Intent.ACTION_PACKAGE_CHANGED; +import static android.content.Intent.ACTION_PACKAGE_REMOVED; +import static android.content.Intent.ACTION_USER_SWITCHED; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_OWNED_CHANGE_IDS; +import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_REMOVE_OVERRIDES; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.compat.PackageOverride; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Handler; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.Properties; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import com.android.internal.compat.CompatibilityOverrideConfig; +import com.android.internal.compat.CompatibilityOverridesToRemoveConfig; +import com.android.internal.compat.IPlatformCompat; +import com.android.server.testables.TestableDeviceConfig.TestableDeviceConfigRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +/** + * Test class for {@link AppCompatOverridesService}. + * + * Build/Install/Run: + * atest FrameworksMockingServicesTests:AppCompatOverridesServiceTest + */ +@RunWith(MockitoJUnitRunner.class) +@SmallTest +@Presubmit +public class AppCompatOverridesServiceTest { + private static final String NAMESPACE_1 = "namespace_1"; + private static final String NAMESPACE_2 = "namespace_2"; + private static final String NAMESPACE_3 = "namespace_3"; + private static final List<String> SUPPORTED_NAMESPACES = Arrays.asList(NAMESPACE_1, + NAMESPACE_2, NAMESPACE_3); + + private static final String PACKAGE_1 = "com.android.test1"; + private static final String PACKAGE_2 = "com.android.test2"; + private static final String PACKAGE_3 = "com.android.test3"; + private static final String PACKAGE_4 = "com.android.test4"; + + private MockContext mMockContext; + private BroadcastReceiver mPackageReceiver; + private AppCompatOverridesService mService; + + @Mock + private PackageManager mPackageManager; + @Mock + private IPlatformCompat mPlatformCompat; + + @Captor + private ArgumentCaptor<CompatibilityOverrideConfig> mOverridesToAddConfigCaptor; + @Captor + private ArgumentCaptor<CompatibilityOverridesToRemoveConfig> mOverridesToRemoveConfigCaptor; + + @Rule + public TestableDeviceConfigRule mDeviceConfigRule = new TestableDeviceConfigRule(); + + class MockContext extends ContextWrapper { + MockContext(Context base) { + super(base); + } + + @Override + public PackageManager getPackageManager() { + return mPackageManager; + } + + @Override + public Executor getMainExecutor() { + // Run on current thread + return Runnable::run; + } + + @Override + @Nullable + public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver, + @NonNull IntentFilter filter, @Nullable String broadcastPermission, + @Nullable Handler scheduler) { + mPackageReceiver = receiver; + return null; + } + } + + @Before + public void setUp() throws Exception { + mMockContext = new MockContext( + InstrumentationRegistry.getInstrumentation().getTargetContext()); + mService = new AppCompatOverridesService(mMockContext, mPlatformCompat, + SUPPORTED_NAMESPACES); + mService.registerPackageReceiver(); + assertThat(mPackageReceiver).isNotNull(); + } + + @Test + public void onPropertiesChanged_removeOverridesFlagNotSet_appliesPackageOverrides() + throws Exception { + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 3); + mockGetApplicationInfoNotInstalled(PACKAGE_2); + mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 10); + mockGetApplicationInfo(PACKAGE_4, /* versionCode= */ 1); + + mService.registerDeviceConfigListeners(); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "123,456,789") + .setString(PACKAGE_1, "123:::true,456::1:false,456:2::true,789:::false") + .setString(PACKAGE_2, "123:::true") + .setString(PACKAGE_3, "123:1:9:true,123:10:11:false,123:11::true") + .setString(PACKAGE_4, "").build()); + + Map<Long, PackageOverride> addedOverrides; + // Package 1 + verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(), + eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + addedOverrides = mOverridesToAddConfigCaptor.getValue().overrides; + assertThat(addedOverrides).hasSize(3); + assertThat(addedOverrides.get(123L)).isEqualTo( + new PackageOverride.Builder().setEnabled(true).build()); + assertThat(addedOverrides.get(456L)).isEqualTo( + new PackageOverride.Builder().setMinVersionCode(2).setEnabled(true).build()); + assertThat(addedOverrides.get(789L)).isEqualTo( + new PackageOverride.Builder().setEnabled(false).build()); + // Package 2 + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_2)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_2)); + // Package 3 + verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(), + eq(PACKAGE_3)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_3)); + addedOverrides = mOverridesToAddConfigCaptor.getValue().overrides; + assertThat(addedOverrides).hasSize(1); + assertThat(addedOverrides.get(123L)).isEqualTo( + new PackageOverride.Builder().setMinVersionCode(10).setMaxVersionCode( + 11).setEnabled(false).build()); + assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(456L, 789L); + // Package 4 + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_4)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_4)); + assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(123L, 456L, + 789L); + } + + @Test + public void onPropertiesChanged_ownedChangeIdsFlagNotSet_onlyAddsOverrides() + throws Exception { + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mService.registerDeviceConfigListeners(); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "123:::true").build()); + + verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(), + eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(123L); + } + + @Test + public void onPropertiesChanged_removeOverridesFlagSetBefore_skipsOverridesToRemove() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "123,456,789") + .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=123:456," + PACKAGE_2 + "=123") + .setString(PACKAGE_1, "123:::true") + .setString(PACKAGE_4, "123:::true").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0); + mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0); + + mService.registerDeviceConfigListeners(); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "123:::true,789:::false") + .setString(PACKAGE_2, "123:::true") + .setString(PACKAGE_3, "456:::true").build()); + + // Package 1 + verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(), + eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(789L); + // Package 2 + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_2)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_2)); + assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(456L, 789L); + // Package 3 + verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(), + eq(PACKAGE_3)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_3)); + assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(456L); + assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(123L, 789L); + // Package 4 (not applied because it hasn't changed after the listener was added) + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_4)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_4)); + } + + @Test + public void onPropertiesChanged_removeOverridesFlagChangedNoPackageOverridesFlags_removesOnly() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "123,456,789") + .setString(PACKAGE_1, "") + .setString(PACKAGE_2, "").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0); + + mService.registerDeviceConfigListeners(); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_REMOVE_OVERRIDES, + PACKAGE_1 + "=123:456," + PACKAGE_2 + "=*").build()); + + // Package 1 + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, times(2)).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1)); + List<CompatibilityOverridesToRemoveConfig> configs = + mOverridesToRemoveConfigCaptor.getAllValues(); + assertThat(configs.size()).isAtLeast(2); + assertThat(configs.get(configs.size() - 2).changeIds).containsExactly(123L, 456L); + assertThat(configs.get(configs.size() - 1).changeIds).containsExactly(789L); + // Package 2 + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_2)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_2)); + assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(123L, 456L, + 789L); + } + + @Test + public void onPropertiesChanged_removeOverridesFlagAndSomePackageOverrideFlagsChanged_ok() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "123,456,789") + .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=123:456") + .setString(PACKAGE_1, "123:::true,789:::false") + .setString(PACKAGE_3, "456:::false,789:::true").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0); + mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0); + + mService.registerDeviceConfigListeners(); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_2 + "=123," + PACKAGE_3 + "=789") + .setString(PACKAGE_2, "123:::true").build()); + + // Package 1 + verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(), + eq(PACKAGE_1)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1)); + assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(123L, + 789L); + assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(456L); + // Package 2 + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_2)); + verify(mPlatformCompat, times(2)).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_2)); + List<CompatibilityOverridesToRemoveConfig> configs = + mOverridesToRemoveConfigCaptor.getAllValues(); + assertThat(configs.size()).isAtLeast(2); + assertThat(configs.get(configs.size() - 2).changeIds).containsExactly(123L); + assertThat(configs.get(configs.size() - 1).changeIds).containsExactly(456L, 789L); + // Package 3 + verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(), + eq(PACKAGE_3)); + verify(mPlatformCompat, times(2)).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_3)); + assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(456L); + configs = mOverridesToRemoveConfigCaptor.getAllValues(); + assertThat(configs.size()).isAtLeast(2); + assertThat(configs.get(configs.size() - 2).changeIds).containsExactly(789L); + assertThat(configs.get(configs.size() - 1).changeIds).containsExactly(123L); + } + + @Test + public void onPropertiesChanged_ownedChangeIdsFlagAndSomePackageOverrideFlagsChanged_ok() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=*") + .setString(FLAG_OWNED_CHANGE_IDS, "123,456") + .setString(PACKAGE_1, "123:::true") + .setString(PACKAGE_3, "456:::false").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0); + mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0); + + mService.registerDeviceConfigListeners(); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "123,456,789") + .setString(PACKAGE_2, "123:::true").build()); + + // Package 1 + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1)); + assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(123L, 456L, + 789L); + // Package 2 + verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(), + eq(PACKAGE_2)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_2)); + assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(123L); + assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(456L, 789L); + // Package 3 + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_3)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_3)); + } + + @Test + public void onPropertiesChanged_platformCompatThrowsExceptionForSomeCalls_skipsFailedCalls() + throws Exception { + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0); + mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0); + mockGetApplicationInfo(PACKAGE_4, /* versionCode= */ 0); + doThrow(new RemoteException()).when(mPlatformCompat).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_2)); + doThrow(new RemoteException()).when(mPlatformCompat).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_3)); + + mService.registerDeviceConfigListeners(); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "123,456") + .setString(PACKAGE_1, "123:::true") + .setString(PACKAGE_2, "123:::true") + .setString(PACKAGE_3, "123:::true") + .setString(PACKAGE_4, "123:::true").build()); + + // Package 1 + verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class), + eq(PACKAGE_1)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + // Package 2 + verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class), + eq(PACKAGE_2)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_2)); + // Package 3 + verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class), + eq(PACKAGE_3)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_3)); + // Package 4 + verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class), + eq(PACKAGE_1)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_4)); + } + + @Test + public void packageReceiver_packageAddedIntentDataIsNull_doesNothing() throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_1, "201:::true").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, new Intent(ACTION_PACKAGE_ADDED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + @Test + public void packageReceiver_actionIsNull_doesNothing() throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_1, "201:::true").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, /* action= */ null)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + @Test + public void packageReceiver_unsupportedAction_doesNothing() throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_1, "201:::true").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_USER_SWITCHED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + @Test + public void packageReceiver_packageAddedIntentPackageNotInstalled_doesNothing() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_1, "201:::true").build()); + mockGetApplicationInfoNotInstalled(PACKAGE_1); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + @Test + public void packageReceiver_packageAddedIntentNoOverridesForPackage_doesNothing() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_2, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_3, "201:::true").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + @Test + public void packageReceiver_packageAddedIntent_appliesOverridesFromAllNamespaces() + throws Exception { + // We're adding the owned_change_ids flag to make sure it's ignored. + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "101,102,103") + .setString(PACKAGE_1, "101:::true") + .setString(PACKAGE_2, "102:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(FLAG_OWNED_CHANGE_IDS, "201,202,203") + .setString(PACKAGE_3, "201:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3) + .setString(FLAG_OWNED_CHANGE_IDS, "301,302") + .setString(PACKAGE_1, "301:::true,302:::false") + .setString(PACKAGE_2, "302:::false").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED)); + + verify(mPlatformCompat, times(2)).putOverridesOnReleaseBuilds( + mOverridesToAddConfigCaptor.capture(), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + List<CompatibilityOverrideConfig> configs = mOverridesToAddConfigCaptor.getAllValues(); + assertThat(configs.get(0).overrides.keySet()).containsExactly(101L); + assertThat(configs.get(1).overrides.keySet()).containsExactly(301L, 302L); + } + + @Test + public void packageReceiver_packageChangedIntent_appliesOverrides() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "101:::true,103:::false").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_CHANGED)); + + verify(mPlatformCompat).putOverridesOnReleaseBuilds( + mOverridesToAddConfigCaptor.capture(), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(101L, + 103L); + } + + @Test + public void packageReceiver_packageAddedIntentRemoveOverridesSetForSomeNamespaces_skipsIds() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=103," + PACKAGE_2 + "=101") + .setString(PACKAGE_1, "101:::true,103:::false") + .setString(PACKAGE_2, "102:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_1, "201:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3) + .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=301," + PACKAGE_3 + "=302") + .setString(PACKAGE_1, "301:::true,302:::false,303:::true") + .setString(PACKAGE_3, "302:::false").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED)); + + verify(mPlatformCompat, times(3)).putOverridesOnReleaseBuilds( + mOverridesToAddConfigCaptor.capture(), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + List<CompatibilityOverrideConfig> configs = mOverridesToAddConfigCaptor.getAllValues(); + assertThat(configs.get(0).overrides.keySet()).containsExactly(101L); + assertThat(configs.get(1).overrides.keySet()).containsExactly(201L); + assertThat(configs.get(2).overrides.keySet()).containsExactly(302L, 303L); + } + + @Test + public void packageReceiver_packageRemovedIntentNoOverridesForPackage_doesNothing() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "101,102") + .setString(PACKAGE_2, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(FLAG_OWNED_CHANGE_IDS, "201,202") + .setString(PACKAGE_3, "201:::true").build()); + mockGetApplicationInfoNotInstalled(PACKAGE_1); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_REMOVED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + @Test + public void packageReceiver_packageRemovedIntentPackageInstalledForAnotherUser_doesNothing() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "101,102,103") + .setString(PACKAGE_1, "101:::true,103:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(FLAG_OWNED_CHANGE_IDS, "201,202") + .setString(PACKAGE_1, "202:::false").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_REMOVED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + @Test + public void packageReceiver_packageRemovedIntent_removesOwnedOverridesForNamespacesWithPackage() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "101,102,103") + .setString(PACKAGE_1, "101:::true,103:::false") + .setString(PACKAGE_2, "102:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(FLAG_OWNED_CHANGE_IDS, "201") + .setString(PACKAGE_3, "201:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3) + .setString(FLAG_OWNED_CHANGE_IDS, "301,302") + .setString(PACKAGE_1, "302:::false") + .setString(PACKAGE_2, "301:::true").build()); + mockGetApplicationInfoNotInstalled(PACKAGE_1); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_REMOVED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, times(2)).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1)); + List<CompatibilityOverridesToRemoveConfig> configs = + mOverridesToRemoveConfigCaptor.getAllValues(); + assertThat(configs.get(0).changeIds).containsExactly(101L, 102L, 103L); + assertThat(configs.get(1).changeIds).containsExactly(301L, 302L); + } + + @Test + public void packageReceiver_packageRemovedIntentNoOwnedIdsForSomeNamespace_skipsNamespace() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "101,102") + .setString(PACKAGE_1, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_1, "201:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3) + .setString(FLAG_OWNED_CHANGE_IDS, "301") + .setString(PACKAGE_1, "301:::true").build()); + mockGetApplicationInfoNotInstalled(PACKAGE_1); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_REMOVED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, times(2)).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1)); + List<CompatibilityOverridesToRemoveConfig> configs = + mOverridesToRemoveConfigCaptor.getAllValues(); + assertThat(configs.get(0).changeIds).containsExactly(101L, 102L); + assertThat(configs.get(1).changeIds).containsExactly(301L); + } + + @Test + public void packageReceiver_platformCompatThrowsExceptionForSomeNamespace_skipsFailedCall() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_1, "201:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3) + .setString(PACKAGE_1, "301:::true").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + doThrow(new RemoteException()).when(mPlatformCompat).putOverridesOnReleaseBuilds( + argThat(config -> config.overrides.containsKey(201L)), eq(PACKAGE_1)); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED)); + + verify(mPlatformCompat, times(3)).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + private void mockGetApplicationInfo(String packageName, long versionCode) + throws Exception { + when(mPackageManager.getApplicationInfo(eq(packageName), anyInt())).thenReturn( + createAppInfo(versionCode)); + } + + private void mockGetApplicationInfoNotInstalled(String packageName) throws Exception { + when(mPackageManager.getApplicationInfo(eq(packageName), anyInt())) + .thenThrow(new PackageManager.NameNotFoundException()); + } + + private static ApplicationInfo createAppInfo(long versionCode) { + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.longVersionCode = versionCode; + return appInfo; + } + + private Intent createPackageIntent(String packageName, @Nullable String action) { + return new Intent(action, Uri.parse("package:" + packageName)); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/OWNERS b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/OWNERS new file mode 100644 index 000000000000..6e8aefc4bc01 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/compat/overrides/OWNERS diff --git a/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java b/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java index 589a3497435e..457c8db9fdf3 100644 --- a/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java @@ -48,7 +48,7 @@ import org.mockito.MockitoSession; import org.mockito.quality.Strictness; /** - * Run it as {@code atest FrameworksMockingCoreTests:FactoryResetterTest} + * Run it as {@code atest FrameworksMockingServicesTests:FactoryResetterTest} */ @Presubmit public final class FactoryResetterTest { diff --git a/services/tests/mockingservicestests/src/com/android/server/devicepolicy/OwnerShellDataTest.java b/services/tests/mockingservicestests/src/com/android/server/devicepolicy/OwnerShellDataTest.java new file mode 100644 index 000000000000..dd67d7208034 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/devicepolicy/OwnerShellDataTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2021 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.devicepolicy; + +import static android.os.UserHandle.USER_NULL; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.testng.Assert.expectThrows; + +import android.content.ComponentName; + +import org.junit.Test; + +/** + * Run it as {@code atest FrameworksMockingServicesTests:OwnerShellDataTest} + */ +public final class OwnerShellDataTest { + + private static final int USER_ID = 007; + private static final int PARENT_USER_ID = 'M' + 'I' + 6; + private static final ComponentName ADMIN = new ComponentName("Bond", "James"); + + @Test + public void testForDeviceOwner_noAdmin() { + expectThrows(NullPointerException.class, + () -> OwnerShellData.forDeviceOwner(USER_ID, /* admin= */ null)); + } + + @Test + public void testForDeviceOwner_invalidUser() { + expectThrows(IllegalArgumentException.class, + () -> OwnerShellData.forDeviceOwner(USER_NULL, ADMIN)); + } + + @Test + public void testForDeviceOwner() { + OwnerShellData dto = OwnerShellData.forDeviceOwner(USER_ID, ADMIN); + + assertWithMessage("dto(%s).userId", dto).that(dto.userId).isEqualTo(USER_ID); + assertWithMessage("dto(%s).parentUserId", dto).that(dto.parentUserId) + .isEqualTo(USER_NULL); + assertWithMessage("dto(%s).admin", dto).that(dto.admin).isSameInstanceAs(ADMIN); + assertWithMessage("dto(%s).isDeviceOwner", dto).that(dto.isDeviceOwner).isTrue(); + assertWithMessage("dto(%s).isProfileOwner", dto).that(dto.isProfileOwner).isFalse(); + assertWithMessage("dto(%s).isManagedProfileOwner", dto).that(dto.isManagedProfileOwner) + .isFalse(); + assertWithMessage("dto(%s).isAffiliated", dto).that(dto.isAffiliated).isFalse(); + } + + @Test + public void testForUserProfileOwner_noAdmin() { + expectThrows(NullPointerException.class, + () -> OwnerShellData.forUserProfileOwner(USER_ID, /* admin= */ null)); + } + + @Test + public void testForUserProfileOwner_invalidUser() { + expectThrows(IllegalArgumentException.class, + () -> OwnerShellData.forUserProfileOwner(USER_NULL, ADMIN)); + } + + @Test + public void testForUserProfileOwner() { + OwnerShellData dto = OwnerShellData.forUserProfileOwner(USER_ID, ADMIN); + + assertWithMessage("dto(%s).userId", dto).that(dto.userId).isEqualTo(USER_ID); + assertWithMessage("dto(%s).parentUserId", dto).that(dto.parentUserId) + .isEqualTo(USER_NULL); + assertWithMessage("dto(%s).admin", dto).that(dto.admin).isSameInstanceAs(ADMIN); + assertWithMessage("dto(%s).isDeviceOwner", dto).that(dto.isDeviceOwner).isFalse(); + assertWithMessage("dto(%s).isProfileOwner", dto).that(dto.isProfileOwner).isTrue(); + assertWithMessage("dto(%s).isManagedProfileOwner", dto).that(dto.isManagedProfileOwner) + .isFalse(); + assertWithMessage("dto(%s).isAffiliated", dto).that(dto.isAffiliated).isFalse(); + } + + @Test + public void testForManagedProfileOwner_noAdmin() { + expectThrows(NullPointerException.class, + () -> OwnerShellData.forManagedProfileOwner(USER_ID, PARENT_USER_ID, null)); + } + + @Test + public void testForManagedProfileOwner_invalidUser() { + expectThrows(IllegalArgumentException.class, + () -> OwnerShellData.forManagedProfileOwner(USER_NULL, PARENT_USER_ID, ADMIN)); + } + + @Test + public void testForManagedProfileOwner_invalidParent() { + expectThrows(IllegalArgumentException.class, + () -> OwnerShellData.forManagedProfileOwner(USER_ID, USER_NULL, ADMIN)); + } + + @Test + public void testForManagedProfileOwner_parentOfItself() { + expectThrows(IllegalArgumentException.class, + () -> OwnerShellData.forManagedProfileOwner(USER_ID, USER_ID, ADMIN)); + } + + @Test + public void testForManagedProfileOwner() { + OwnerShellData dto = OwnerShellData.forManagedProfileOwner(USER_ID, PARENT_USER_ID, ADMIN); + + assertWithMessage("dto(%s).userId", dto).that(dto.userId).isEqualTo(USER_ID); + assertWithMessage("dto(%s).parentUserId", dto).that(dto.parentUserId) + .isEqualTo(PARENT_USER_ID); + assertWithMessage("dto(%s).admin", dto).that(dto.admin).isSameInstanceAs(ADMIN); + assertWithMessage("dto(%s).isDeviceOwner", dto).that(dto.isDeviceOwner).isFalse(); + assertWithMessage("dto(%s).isProfileOwner", dto).that(dto.isProfileOwner).isFalse(); + assertWithMessage("dto(%s).isManagedProfileOwner", dto).that(dto.isManagedProfileOwner) + .isTrue(); + assertWithMessage("dto(%s).isAffiliated", dto).that(dto.isAffiliated).isFalse(); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index 0efcc57eeec2..28cdd6317e5a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -134,6 +134,20 @@ public class LocalDisplayAdapterTest { when(mMockedResources.getFloat(com.android.internal.R.dimen .config_screenBrightnessSettingMaximumFloat)) .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[1]); + when(mMockedResources.getStringArray(R.array.config_displayUniqueIdArray)) + .thenReturn(new String[]{}); + TypedArray mockArray = mock(TypedArray.class); + when(mockArray.length()).thenReturn(0); + when(mMockedResources.obtainTypedArray(R.array.config_maskBuiltInDisplayCutoutArray)) + .thenReturn(mockArray); + when(mMockedResources.obtainTypedArray(R.array.config_waterfallCutoutArray)) + .thenReturn(mockArray); + when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerRadiusArray)) + .thenReturn(mockArray); + when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerTopRadiusArray)) + .thenReturn(mockArray); + when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerBottomRadiusArray)) + .thenReturn(mockArray); } @After diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java index e2e7f5dd7ba2..94dcdf92d9d4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java @@ -58,72 +58,86 @@ public class LocationAttributionHelperTest { @Test public void testLocationMonitoring() { CallerIdentity caller1 = CallerIdentity.forTest(1, 1, "test1", null); - Object key1 = new Object(); - Object key2 = new Object(); CallerIdentity caller2 = CallerIdentity.forTest(2, 2, "test2", null); - Object key3 = new Object(); - Object key4 = new Object(); - - mHelper.reportLocationStart(caller1, "gps", key1); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); - - mHelper.reportLocationStart(caller1, "gps", key2); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); - - mHelper.reportLocationStart(caller2, "gps", key3); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); - - mHelper.reportLocationStart(caller2, "gps", key4); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); - - mHelper.reportLocationStop(caller1, "gps", key2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); - mHelper.reportLocationStop(caller1, "gps", key1); - verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller1); - - mHelper.reportLocationStop(caller2, "gps", key3); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); - mHelper.reportLocationStop(caller2, "gps", key4); - verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller2); + + mHelper.reportLocationStart(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportLocationStart(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportLocationStart(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + + mHelper.reportLocationStart(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + + mHelper.reportLocationStop(caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + mHelper.reportLocationStop(caller1); + verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, CallerIdentity.forAggregation(caller1)); + + mHelper.reportLocationStop(caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + mHelper.reportLocationStop(caller2); + verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, CallerIdentity.forAggregation(caller2)); } @Test public void testHighPowerLocationMonitoring() { CallerIdentity caller1 = CallerIdentity.forTest(1, 1, "test1", null); - Object key1 = new Object(); - Object key2 = new Object(); CallerIdentity caller2 = CallerIdentity.forTest(2, 2, "test2", null); - Object key3 = new Object(); - Object key4 = new Object(); - - mHelper.reportHighPowerLocationStart(caller1, "gps", key1); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - - mHelper.reportHighPowerLocationStart(caller1, "gps", key2); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - - mHelper.reportHighPowerLocationStart(caller2, "gps", key3); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - - mHelper.reportHighPowerLocationStart(caller2, "gps", key4); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - - mHelper.reportHighPowerLocationStop(caller1, "gps", key2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - mHelper.reportHighPowerLocationStop(caller1, "gps", key1); - verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - - mHelper.reportHighPowerLocationStop(caller2, "gps", key3); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - mHelper.reportHighPowerLocationStop(caller2, "gps", key4); - verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); + + mHelper.reportHighPowerLocationStart(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportHighPowerLocationStart(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportHighPowerLocationStart(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + + mHelper.reportHighPowerLocationStart(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + + mHelper.reportHighPowerLocationStop(caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + mHelper.reportHighPowerLocationStop(caller1); + verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportHighPowerLocationStop(caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + mHelper.reportHighPowerLocationStop(caller2); + verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java index d0b2edadc714..890a5495ef16 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java @@ -845,6 +845,48 @@ public class LocationProviderManagerTest { } @Test + public void testLocationMonitoring_multipleIdentities() { + CallerIdentity identity1 = CallerIdentity.forTest(CURRENT_USER, 1, + "mypackage", "attribution", "listener1"); + CallerIdentity identity2 = CallerIdentity.forTest(CURRENT_USER, 1, + "mypackage", "attribution", "listener2"); + + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION, + IDENTITY.getPackageName())).isFalse(); + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION, + IDENTITY.getPackageName())).isFalse(); + + ILocationListener listener1 = createMockLocationListener(); + LocationRequest request1 = new LocationRequest.Builder(0).setWorkSource( + WORK_SOURCE).build(); + mManager.registerLocationRequest(request1, identity1, PERMISSION_FINE, listener1); + + ILocationListener listener2 = createMockLocationListener(); + LocationRequest request2 = new LocationRequest.Builder(0).setWorkSource( + WORK_SOURCE).build(); + mManager.registerLocationRequest(request2, identity2, PERMISSION_FINE, listener2); + + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION, + "mypackage")).isTrue(); + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION, + "mypackage")).isTrue(); + + mManager.unregisterLocationRequest(listener2); + + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION, + "mypackage")).isTrue(); + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION, + "mypackage")).isTrue(); + + mManager.unregisterLocationRequest(listener1); + + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION, + "mypackage")).isFalse(); + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION, + "mypackage")).isFalse(); + } + + @Test public void testProviderRequest() { assertThat(mProvider.getRequest().isActive()).isFalse(); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java index 4d6f49e5d223..4eba21934a4e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java @@ -90,6 +90,19 @@ public class StationaryThrottlingLocationProviderTest { } @Test + public void testThrottle_lowInterval() { + ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(0).build(); + + mProvider.getController().setRequest(request); + mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom)); + verify(mListener, times(1)).onReportLocation(any(LocationResult.class)); + + mInjector.getDeviceStationaryHelper().setStationary(true); + mInjector.getDeviceIdleHelper().setIdle(true); + verify(mListener, after(1500).times(2)).onReportLocation(any(LocationResult.class)); + } + + @Test public void testThrottle_stationaryExit() { ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build(); @@ -104,17 +117,16 @@ public class StationaryThrottlingLocationProviderTest { mInjector.getDeviceIdleHelper().setIdle(true); verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST); - verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class)); - verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class)); + verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class)); mInjector.getDeviceStationaryHelper().setStationary(false); verify(mDelegate, times(2)).onSetRequest(request); - verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class)); + verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class)); } @Test public void testThrottle_idleExit() { - ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build(); + ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(1000).build(); mProvider.getController().setRequest(request); verify(mDelegate).onSetRequest(request); @@ -127,17 +139,16 @@ public class StationaryThrottlingLocationProviderTest { mInjector.getDeviceStationaryHelper().setStationary(true); verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST); - verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class)); - verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class)); + verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class)); mInjector.getDeviceIdleHelper().setIdle(false); verify(mDelegate, times(2)).onSetRequest(request); - verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class)); + verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class)); } @Test public void testThrottle_NoInitialLocation() { - ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build(); + ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(1000).build(); mProvider.getController().setRequest(request); verify(mDelegate).onSetRequest(request); @@ -149,11 +160,11 @@ public class StationaryThrottlingLocationProviderTest { mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom)); verify(mListener, times(1)).onReportLocation(any(LocationResult.class)); verify(mDelegate, times(1)).onSetRequest(ProviderRequest.EMPTY_REQUEST); - verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class)); + verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class)); mInjector.getDeviceStationaryHelper().setStationary(false); verify(mDelegate, times(2)).onSetRequest(request); - verify(mListener, after(75).times(2)).onReportLocation(any(LocationResult.class)); + verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class)); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt index 72bc77ebb9ef..e053dc3fd32e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt @@ -16,8 +16,10 @@ package com.android.server.pm +import android.content.Context import android.os.Build import android.os.Handler +import android.os.PowerManager import android.provider.DeviceConfig import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION import android.testing.AndroidTestingRunner @@ -27,6 +29,7 @@ import com.android.server.apphibernation.AppHibernationManagerInternal import com.android.server.apphibernation.AppHibernationService import com.android.server.extendedtestutils.wheneverStatic import com.android.server.testutils.whenever +import org.junit.Assert import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -55,6 +58,8 @@ class PackageManagerServiceHibernationTests { @Mock lateinit var appHibernationManager: AppHibernationManagerInternal + @Mock + lateinit var powerManager: PowerManager @Before @Throws(Exception::class) @@ -68,6 +73,24 @@ class PackageManagerServiceHibernationTests { .thenReturn(appHibernationManager) whenever(rule.mocks().injector.handler) .thenReturn(Handler(TestableLooper.get(this).looper)) + val injector = object : PackageDexOptimizer.Injector { + override fun getAppHibernationManagerInternal(): AppHibernationManagerInternal { + return appHibernationManager + } + + override fun getPowerManager(context: Context?): PowerManager { + return powerManager + } + } + val packageDexOptimizer = PackageDexOptimizer( + injector, + rule.mocks().installer, + rule.mocks().installLock, + rule.mocks().context, + "*dexopt*") + whenever(rule.mocks().injector.packageDexOptimizer) + .thenReturn(packageDexOptimizer) + whenever(appHibernationManager.isOatArtifactDeletionEnabled).thenReturn(true) } @Test @@ -78,8 +101,11 @@ class PackageManagerServiceHibernationTests { rule.system().dataAppDirectory) val pm = createPackageManagerService() rule.system().validateFinalState() - val ps = pm.getPackageSetting(TEST_PACKAGE_NAME) - ps!!.setStopped(true, TEST_USER_ID) + + TestableLooper.get(this).processAllMessages() + + whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID)) + .thenReturn(true) pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID) @@ -90,6 +116,31 @@ class PackageManagerServiceHibernationTests { } @Test + fun testExitForceStop_nonExistingAppHibernationManager_doesNotThrowException() { + whenever(rule.mocks().injector.getLocalService(AppHibernationManagerInternal::class.java)) + .thenReturn(null) + + rule.system().stageScanExistingPackage( + TEST_PACKAGE_NAME, + 1L, + rule.system().dataAppDirectory) + val pm = createPackageManagerService() + rule.system().validateFinalState() + + TestableLooper.get(this).processAllMessages() + + whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID)) + .thenReturn(true) + + try { + pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID) + TestableLooper.get(this).processAllMessages() + } catch (e: Exception) { + Assert.fail("Method throws exception when AppHibernationManager is not ready.\n$e") + } + } + + @Test fun testGetOptimizablePackages_ExcludesGloballyHibernatingPackages() { rule.system().stageScanExistingPackage( TEST_PACKAGE_NAME, diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java index 64b24c12f046..43188f630729 100644 --- a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java +++ b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java @@ -95,18 +95,25 @@ public final class TestableDeviceConfig implements StaticMockFixture { String name = invocationOnMock.getArgument(1); String value = invocationOnMock.getArgument(2); mKeyValueMap.put(getKey(namespace, name), value); - for (DeviceConfig.OnPropertiesChangedListener listener : - mOnPropertiesChangedListenerMap.keySet()) { - if (namespace.equals(mOnPropertiesChangedListenerMap.get(listener).first)) { - mOnPropertiesChangedListenerMap.get(listener).second.execute( - () -> listener.onPropertiesChanged( - getProperties(namespace, name, value))); - } - } + invokeListeners(namespace, getProperties(namespace, name, value)); return true; } ).when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(), anyBoolean())); + doAnswer((Answer<Boolean>) invocationOnMock -> { + Properties properties = invocationOnMock.getArgument(0); + String namespace = properties.getNamespace(); + Map<String, String> keyValues = new ArrayMap<>(); + for (String name : properties.getKeyset()) { + String value = properties.getString(name, /* defaultValue= */ ""); + mKeyValueMap.put(getKey(namespace, name), value); + keyValues.put(name.toLowerCase(), value); + } + invokeListeners(namespace, getProperties(namespace, keyValues)); + return true; + } + ).when(() -> DeviceConfig.setProperties(any(Properties.class))); + doAnswer((Answer<String>) invocationOnMock -> { String namespace = invocationOnMock.getArgument(0); String name = invocationOnMock.getArgument(1); @@ -153,6 +160,16 @@ public final class TestableDeviceConfig implements StaticMockFixture { return Pair.create(values[0], values[1]); } + private void invokeListeners(String namespace, Properties properties) { + for (DeviceConfig.OnPropertiesChangedListener listener : + mOnPropertiesChangedListenerMap.keySet()) { + if (namespace.equals(mOnPropertiesChangedListenerMap.get(listener).first)) { + mOnPropertiesChangedListenerMap.get(listener).second.execute( + () -> listener.onPropertiesChanged(properties)); + } + } + } + private Properties getProperties(String namespace, String name, String value) { return getProperties(namespace, Collections.singletonMap(name.toLowerCase(), value)); } diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java index 0e40669cf870..d68b81490f6e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import android.app.ActivityThread; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; +import android.provider.DeviceConfig.BadConfigException; import android.provider.DeviceConfig.Properties; import androidx.test.filters.SmallTest; @@ -92,6 +93,16 @@ public class TestableDeviceConfigTest { } @Test + public void setProperties() throws BadConfigException { + String newKey = "key2"; + String newValue = "value2"; + DeviceConfig.setProperties(new Properties.Builder(sNamespace).setString(sKey, + sValue).setString(newKey, newValue).build()); + assertThat(DeviceConfig.getProperty(sNamespace, sKey)).isEqualTo(sValue); + assertThat(DeviceConfig.getProperty(sNamespace, newKey)).isEqualTo(newValue); + } + + @Test public void getProperties_empty() { String newKey = "key2"; String newValue = "value2"; @@ -131,13 +142,12 @@ public class TestableDeviceConfigTest { } @Test - public void testListener() throws InterruptedException { + public void testListener_setProperty() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); OnPropertiesChangedListener changeListener = (properties) -> { assertThat(properties.getNamespace()).isEqualTo(sNamespace); - assertThat(properties.getKeyset().size()).isEqualTo(1); - assertThat(properties.getKeyset()).contains(sKey); + assertThat(properties.getKeyset()).containsExactly(sKey); assertThat(properties.getString(sKey, "bogus_value")).isEqualTo(sValue); assertThat(properties.getString("bogus_key", "bogus_value")).isEqualTo("bogus_value"); countDownLatch.countDown(); @@ -153,6 +163,32 @@ public class TestableDeviceConfigTest { } } + @Test + public void testListener_setProperties() throws BadConfigException, InterruptedException { + CountDownLatch countDownLatch = new CountDownLatch(1); + String newKey = "key2"; + String newValue = "value2"; + + OnPropertiesChangedListener changeListener = (properties) -> { + assertThat(properties.getNamespace()).isEqualTo(sNamespace); + assertThat(properties.getKeyset()).containsExactly(sKey, newKey); + assertThat(properties.getString(sKey, "bogus_value")).isEqualTo(sValue); + assertThat(properties.getString(newKey, "bogus_value")).isEqualTo(newValue); + assertThat(properties.getString("bogus_key", "bogus_value")).isEqualTo("bogus_value"); + countDownLatch.countDown(); + }; + try { + DeviceConfig.addOnPropertiesChangedListener(sNamespace, + ActivityThread.currentApplication().getMainExecutor(), changeListener); + DeviceConfig.setProperties(new Properties.Builder(sNamespace).setString(sKey, + sValue).setString(newKey, newValue).build()); + assertThat(countDownLatch.await( + WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue(); + } finally { + DeviceConfig.removeOnPropertiesChangedListener(changeListener); + } + } + } diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/LocalColorRepositoryTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/LocalColorRepositoryTest.java new file mode 100644 index 000000000000..ea7a9a45facd --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/LocalColorRepositoryTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2021 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.wallpaper; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import static java.util.Arrays.asList; + +import android.app.ILocalWallpaperColorConsumer; +import android.app.WallpaperColors; +import android.graphics.RectF; +import android.os.IBinder; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; + +import android.util.ArraySet; +import java.util.List; +import java.util.function.Consumer; + + +@RunWith(AndroidJUnit4.class) +public class LocalColorRepositoryTest { + private LocalColorRepository mRepo = new LocalColorRepository(); + @Mock + private IBinder mBinder1; + @Mock + private IBinder mBinder2; + @Mock + private ILocalWallpaperColorConsumer mCallback1; + @Mock + private ILocalWallpaperColorConsumer mCallback2; + + @Before + public void setUp() { + initMocks(this); + when(mCallback1.asBinder()).thenReturn(mBinder1); + when(mCallback2.asBinder()).thenReturn(mBinder2); + } + + @Test + public void testDisplayAreas() { + RectF area1 = new RectF(1, 0, 0, 0); + RectF area2 = new RectF(2, 1, 1, 1); + ArraySet<RectF> expectedAreas = new ArraySet(asList(area1, area2)); + + mRepo.addAreas(mCallback1, asList(area1), 0); + mRepo.addAreas(mCallback2, asList(area2), 0); + mRepo.addAreas(mCallback1, asList(new RectF(3, 1, 1, 1)), 1); + + assertEquals(expectedAreas, new ArraySet(mRepo.getAreasByDisplayId(0))); + assertEquals(new ArraySet(asList(new RectF(3, 1, 1, 1))), + new ArraySet(mRepo.getAreasByDisplayId(1))); + assertEquals(new ArraySet(), new ArraySet(mRepo.getAreasByDisplayId(2))); + } + + @Test + public void testAddAndRemoveAreas() { + RectF area1 = new RectF(1, 0, 0, 0); + RectF area2 = new RectF(2, 1, 1, 1); + + mRepo.addAreas(mCallback1, asList(area1), 0); + mRepo.addAreas(mCallback1, asList(area2), 0); + mRepo.addAreas(mCallback2, asList(area2), 1); + + List<RectF> removed = mRepo.removeAreas(mCallback1, asList(area1), 0); + assertEquals(new ArraySet(asList(area1)), new ArraySet(removed)); + // since we have another callback with a different area, we don't purge rid of any areas + removed = mRepo.removeAreas(mCallback1, asList(area2), 0); + assertEquals(new ArraySet(), new ArraySet(removed)); + } + + @Test + public void testAreaCallback() { + Consumer<ILocalWallpaperColorConsumer> consumer = mock(Consumer.class); + WallpaperColors colors = mock(WallpaperColors.class); + RectF area1 = new RectF(1, 0, 0, 0); + RectF area2 = new RectF(2, 1, 1, 1); + + mRepo.addAreas(mCallback1, asList(area1), 0); + mRepo.addAreas(mCallback1, asList(area2), 0); + mRepo.addAreas(mCallback2, asList(area2), 0); + + mRepo.forEachCallback(consumer, area1, 0); + Mockito.verify(consumer, times(1)).accept(eq(mCallback1)); + Mockito.verify(consumer, times(0)).accept(eq(mCallback2)); + mRepo.forEachCallback(consumer, area2, 0); + Mockito.verify(consumer, times(2)).accept(eq(mCallback1)); + Mockito.verify(consumer, times(1)).accept(eq(mCallback2)); + } + + @Test + public void unregisterCallbackWhenNoAreas() { + RectF area1 = new RectF(1, 0, 0, 0); + RectF area2 = new RectF(2, 1, 1, 1); + + assertFalse(mRepo.isCallbackAvailable(mCallback1)); + + mRepo.addAreas(mCallback1, asList(area1), 0); + mRepo.addAreas(mCallback1, asList(area2), 0); + + mRepo.removeAreas(mCallback1, asList(area1, area2), 0); + assertFalse(mRepo.isCallbackAvailable(mCallback1)); + + mRepo.addAreas(mCallback1, asList(area1), 0); + assertTrue(mRepo.isCallbackAvailable(mCallback1)); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index a49afc75c739..fe23c14a0b95 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -152,6 +152,7 @@ public class WallpaperManagerServiceTests { sContext.getTestablePermissions().setPermission( android.Manifest.permission.SET_WALLPAPER, PackageManager.PERMISSION_GRANTED); + doNothing().when(sContext).sendBroadcastAsUser(any(), any()); //Wallpaper components sWallpaperService = mock(IWallpaperConnection.Stub.class); @@ -188,7 +189,6 @@ public class WallpaperManagerServiceTests { MockitoAnnotations.initMocks(this); sContext.addMockSystemService(DisplayManager.class, mDisplayManager); - doNothing().when(sContext).sendBroadcastAsUser(any(), any()); final Display mockDisplay = mock(Display.class); doReturn(DISPLAY_SIZE_DIMENSION).when(mockDisplay).getMaximumSizeDimension(); diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 339a5f916b4b..7020744d661c 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -110,6 +110,7 @@ android_test { data: [ ":JobTestApp", + ":StubTestApp", ], java_resources: [ diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index bcb2cf8fa0c3..68b84693f0ef 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -71,6 +71,7 @@ <uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"/> <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/> <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/> + <uses-permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"/> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/> <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/> <uses-permission android:name="android.permission.HARDWARE_TEST"/> diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index 5a0f1ee963a2..bb3eb81df6ed 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -16,6 +16,13 @@ <configuration description="Runs Frameworks Services Tests."> <option name="test-suite-tag" value="apct" /> <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push-file" key="SimpleServiceTestApp3.apk" + value="/data/local/tmp/cts/content/SimpleServiceTestApp3.apk" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> @@ -28,6 +35,17 @@ <option name="test-file-name" value="SimpleServiceTestApp3.apk" /> </target_preparer> + <!-- Create place to store apks --> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="mkdir -p /data/local/tmp/servicestests" /> + <option name="teardown-command" value="rm -rf /data/local/tmp/servicestests"/> + </target_preparer> + + <!-- Load additional APKs onto device --> + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <option name="push" value="StubTestApp.apk->/data/local/tmp/servicestests/StubTestApp.apk"/> + </target_preparer> + <option name="test-tag" value="FrameworksServicesTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.frameworks.servicestests" /> diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index d6c11a549dfa..e612d121b093 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -65,6 +65,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; import android.content.ComponentName; import android.content.Context; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java index 80e81d6e7cb9..554f0a4265be 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java @@ -29,6 +29,7 @@ import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEA import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -176,7 +177,7 @@ public class AccessibilityInputFilterTest { } @Test - public void testEventHandler_shouldChangeAfterOnDisplayChanged() { + public void testEventHandler_shouldIncreaseAndHaveCorrectOrderAfterOnDisplayAdded() { prepareLooper(); // Check if there is only one mEventHandler when there is one default display. @@ -184,13 +185,51 @@ public class AccessibilityInputFilterTest { assertEquals(1, mEventHandler.size()); // Check if it has correct numbers of mEventHandler for corresponding displays. - setDisplayCount(4); - mA11yInputFilter.onDisplayChanged(); - assertEquals(4, mEventHandler.size()); + setDisplayCount(2); + mA11yInputFilter.onDisplayAdded(mDisplayList.get(SECOND_DISPLAY)); + assertEquals(2, mEventHandler.size()); + + EventStreamTransformation next = mEventHandler.get(SECOND_DISPLAY); + assertNotNull(next); + + // Start from index 1 because KeyboardInterceptor only exists in EventHandler for + // DEFAULT_DISPLAY. + for (int i = 1; next != null; i++) { + assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]); + next = next.getNext(); + } + } + + @Test + public void testEventHandler_shouldDecreaseAfterOnDisplayRemoved() { + prepareLooper(); setDisplayCount(2); - mA11yInputFilter.onDisplayChanged(); + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); assertEquals(2, mEventHandler.size()); + + // Check if it has correct numbers of mEventHandler for corresponding displays. + mA11yInputFilter.onDisplayRemoved(SECOND_DISPLAY); + assertEquals(1, mEventHandler.size()); + + EventStreamTransformation eventHandler = mEventHandler.get(SECOND_DISPLAY); + assertNull(eventHandler); + } + + @Test + public void testEventHandler_shouldNoChangedInOtherDisplayAfterOnDisplayRemoved() { + prepareLooper(); + + setDisplayCount(2); + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + EventStreamTransformation eventHandlerBeforeDisplayRemoved = + mEventHandler.get(DEFAULT_DISPLAY); + + mA11yInputFilter.onDisplayRemoved(SECOND_DISPLAY); + EventStreamTransformation eventHandlerAfterDisplayRemoved = + mEventHandler.get(DEFAULT_DISPLAY); + + assertEquals(eventHandlerBeforeDisplayRemoved, eventHandlerAfterDisplayRemoved); } @Test @@ -240,7 +279,7 @@ public class AccessibilityInputFilterTest { } @Test - public void testInputEvent_shouldClearEventsForAllEventHandlers() { + public void testInputEvent_shouldClearEventsForDisplayEventHandlers() { prepareLooper(); mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); @@ -253,13 +292,71 @@ public class AccessibilityInputFilterTest { send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN)); assertEquals(2, mCaptor1.mEvents.size()); - // InputEvent with different input source should trigger clearEvents() for each - // EventStreamTransformation in EventHandler. + // InputEvent with different input source to the same display should trigger + // clearEvents() for the EventHandler in this display. send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_MOUSE)); assertEquals(1, mCaptor1.mEvents.size()); } @Test + public void testInputEvent_shouldNotClearEventsForOtherDisplayEventHandlers() { + prepareLooper(); + + setDisplayCount(2); + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + assertEquals(2, mEventHandler.size()); + + mCaptor1 = new EventCaptor(); + mCaptor2 = new EventCaptor(); + mEventHandler.put(DEFAULT_DISPLAY, mCaptor1); + mEventHandler.put(SECOND_DISPLAY, mCaptor2); + + // InputEvent with different displayId should be dispatched to corresponding EventHandler. + send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN)); + send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN)); + send(downEvent(SECOND_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN)); + + // InputEvent with different input source should not trigger clearEvents() for + // the EventHandler in the other display. + send(downEvent(SECOND_DISPLAY, InputDevice.SOURCE_MOUSE)); + assertEquals(2, mCaptor1.mEvents.size()); + } + + @Test + public void testInputEvent_shouldNotClearEventsForOtherDisplayAfterOnDisplayAdded() { + prepareLooper(); + + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + mCaptor1 = new EventCaptor(); + mEventHandler.put(DEFAULT_DISPLAY, mCaptor1); + + send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN)); + send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN)); + assertEquals(2, mCaptor1.mEvents.size()); + + setDisplayCount(2); + mA11yInputFilter.onDisplayAdded(mDisplayList.get(SECOND_DISPLAY)); + assertEquals(2, mCaptor1.mEvents.size()); + } + + @Test + public void testInputEvent_shouldNotClearEventsForOtherDisplayAfterOnDisplayRemoved() { + prepareLooper(); + + setDisplayCount(2); + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + mCaptor1 = new EventCaptor(); + mEventHandler.put(DEFAULT_DISPLAY, mCaptor1); + + send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN)); + send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN)); + assertEquals(2, mCaptor1.mEvents.size()); + + mA11yInputFilter.onDisplayRemoved(SECOND_DISPLAY); + assertEquals(2, mCaptor1.mEvents.size()); + } + + @Test public void testEnabledFeatures_windowMagnificationMode_expectedMagnificationGestureHandler() { prepareLooper(); doReturn(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW).when( diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 2a5bb18ae428..df975cda54a5 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -40,7 +40,6 @@ import android.graphics.drawable.Icon; import android.os.IBinder; import android.os.UserHandle; import android.provider.Settings; -import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; @@ -54,13 +53,16 @@ import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** * APCT tests for {@link AccessibilityManagerService}. */ -public class AccessibilityManagerServiceTest extends AndroidTestCase { +public class AccessibilityManagerServiceTest { private static final String TAG = "A11Y_MANAGER_SERVICE_TEST"; private static final int ACTION_ID = 20; private static final String LABEL = "label"; @@ -104,8 +106,8 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { private AccessibilityServiceConnection mAccessibilityServiceConnection; private AccessibilityManagerService mA11yms; - @Override - protected void setUp() throws Exception { + @Before + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class); @@ -167,44 +169,48 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { } @SmallTest + @Test public void testRegisterSystemActionWithoutPermission() throws Exception { doThrow(SecurityException.class).when(mMockSecurityPolicy) .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); try { mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID); - fail(); + Assert.fail(); } catch (SecurityException expected) { } verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION); } @SmallTest + @Test public void testRegisterSystemAction() throws Exception { mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID); verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION); } - @SmallTest + @Test public void testUnregisterSystemActionWithoutPermission() throws Exception { doThrow(SecurityException.class).when(mMockSecurityPolicy) .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); try { mA11yms.unregisterSystemAction(ACTION_ID); - fail(); + Assert.fail(); } catch (SecurityException expected) { } verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID); } @SmallTest + @Test public void testUnregisterSystemAction() throws Exception { mA11yms.unregisterSystemAction(ACTION_ID); verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID); } @SmallTest + @Test public void testOnSystemActionsChanged() throws Exception { setupAccessibilityServiceConnection(); mA11yms.notifySystemActionsChangedLocked(mUserState); @@ -213,6 +219,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { } @SmallTest + @Test public void testOnMagnificationTransitionFailed_capabilitiesIsAll_fallBackToPreviousMode() { final AccessibilityUserState userState = mA11yms.mUserStates.get( mA11yms.getCurrentUserIdLocked()); @@ -223,7 +230,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { mA11yms.onMagnificationTransitionEndedLocked(false); - assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, + Assert.assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, userState.getMagnificationModeLocked()); } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java index 00daa5c89fba..432a500a5041 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.GestureDescription; import android.accessibilityservice.IAccessibilityServiceClient; import android.content.ComponentName; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java index e4d51e4374a7..4afe0996e12a 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java @@ -120,6 +120,7 @@ public class AccessibilityWindowManagerTest { @Mock private AccessibilityWindowManager.AccessibilityEventSender mMockA11yEventSender; @Mock private AccessibilitySecurityPolicy mMockA11ySecurityPolicy; @Mock private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager; + @Mock private AccessibilityTraceManager mMockA11yTraceManager; @Mock private IBinder mMockHostToken; @Mock private IBinder mMockEmbeddedToken; @@ -140,7 +141,8 @@ public class AccessibilityWindowManagerTest { mMockWindowManagerInternal, mMockA11yEventSender, mMockA11ySecurityPolicy, - mMockA11yUserManager); + mMockA11yUserManager, + mMockA11yTraceManager); // Starts tracking window of default display and sets the default display // as top focused display before each testing starts. startTrackingPerDisplay(Display.DEFAULT_DISPLAY); @@ -834,6 +836,19 @@ public class AccessibilityWindowManagerTest { assertNull(token); } + @Test + public void onDisplayReparented_shouldRemoveObserver() throws RemoteException { + // Starts tracking window of second display. + startTrackingPerDisplay(SECONDARY_DISPLAY_ID); + assertTrue(mA11yWindowManager.isTrackingWindowsLocked(SECONDARY_DISPLAY_ID)); + // Notifies the second display is an embedded one of the default display. + final WindowsForAccessibilityCallback callbacks = + mCallbackOfWindows.get(Display.DEFAULT_DISPLAY); + callbacks.onDisplayReparented(SECONDARY_DISPLAY_ID); + // Makes sure the observer of the second display is removed. + assertFalse(mA11yWindowManager.isTrackingWindowsLocked(SECONDARY_DISPLAY_ID)); + } + private void registerLeashedTokenAndWindowId() { mA11yWindowManager.registerIdLocked(mMockHostToken, HOST_WINDOW_ID); mA11yWindowManager.registerIdLocked(mMockEmbeddedToken, EMBEDDED_WINDOW_ID); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java index 78e651b7a3c1..c62cae5e9b6e 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java @@ -56,11 +56,13 @@ public class KeyboardInterceptorTest { private MessageCapturingHandler mHandler = new MessageCapturingHandler( msg -> mInterceptor.handleMessage(msg)); @Mock AccessibilityManagerService mMockAms; + @Mock AccessibilityTraceManager mMockTraceManager; @Mock WindowManagerPolicy mMockPolicy; @Before public void setUp() { MockitoAnnotations.initMocks(this); + when(mMockAms.getTraceManager()).thenReturn(mMockTraceManager); mInterceptor = new KeyboardInterceptor(mMockAms, mMockPolicy, mHandler); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java index d4908ee78a1d..59b69f9ffebf 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java @@ -122,6 +122,7 @@ public class MotionEventInjectorTest { MotionEventInjector mMotionEventInjector; IAccessibilityServiceClient mServiceInterface; + AccessibilityTraceManager mTrace; List<GestureStep> mLineList = new ArrayList<>(); List<GestureStep> mClickList = new ArrayList<>(); List<GestureStep> mContinuedLineList1 = new ArrayList<>(); @@ -148,7 +149,8 @@ public class MotionEventInjectorTest { return mMotionEventInjector.handleMessage(msg); } }); - mMotionEventInjector = new MotionEventInjector(mMessageCapturingHandler); + mTrace = mock(AccessibilityTraceManager.class); + mMotionEventInjector = new MotionEventInjector(mMessageCapturingHandler, mTrace); mServiceInterface = mock(IAccessibilityServiceClient.class); mLineList = createSimpleGestureFromPoints(0, 0, false, LINE_DURATION, LINE_START, LINE_END); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java index 8b6b7c235c44..1d6ed038b86d 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java @@ -296,14 +296,6 @@ public class SystemActionPerformerTest { } @Test - public void testToggleSplitScreen_legacy() { - setupWithRealContext(); - mSystemActionPerformer.performSystemAction( - AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN); - verify(mMockStatusBarManagerInternal).toggleSplitScreen(); - } - - @Test public void testScreenshot_requestsFromScreenshotHelper_legacy() { setupWithMockContext(); mSystemActionPerformer.performSystemAction( diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java index 160308762a58..4ce9ba031b25 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; import android.app.UiAutomation; import android.content.Context; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java index 57ee7aa522c2..4a06611f397e 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java @@ -36,6 +36,7 @@ import static com.android.server.accessibility.gestures.TouchState.STATE_TOUCH_E import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityGestureEvent; import android.accessibilityservice.AccessibilityService; @@ -53,6 +54,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.AccessibilityManagerService; +import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.EventStreamTransformation; import com.android.server.accessibility.utils.GestureLogParser; import com.android.server.testutils.OffsettableClock; @@ -107,6 +109,8 @@ public class TouchExplorerTest { @Mock private AccessibilityManagerService mMockAms; + @Mock + private AccessibilityTraceManager mMockTraceManager; @Captor private ArgumentCaptor<AccessibilityGestureEvent> mGestureCaptor; @@ -143,6 +147,7 @@ public class TouchExplorerTest { if (Looper.myLooper() == null) { Looper.prepare(); } + when(mMockAms.getTraceManager()).thenReturn(mMockTraceManager); mContext = InstrumentationRegistry.getContext(); mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); mCaptor = new EventCaptor(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java index 502f64a1e50b..fe4fed9da468 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java @@ -52,6 +52,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.AccessibilityManagerService; +import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks; @@ -93,6 +94,7 @@ public class FullScreenMagnificationControllerTest { mock(FullScreenMagnificationController.ControllerContext.class); final Context mMockContext = mock(Context.class); final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class); + final AccessibilityTraceManager mMockTraceManager = mock(AccessibilityTraceManager.class); final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class); private final MagnificationAnimationCallback mAnimationCallback = mock( MagnificationAnimationCallback.class); @@ -113,9 +115,11 @@ public class FullScreenMagnificationControllerTest { when(mMockContext.getMainLooper()).thenReturn(looper); when(mMockControllerCtx.getContext()).thenReturn(mMockContext); when(mMockControllerCtx.getAms()).thenReturn(mMockAms); + when(mMockControllerCtx.getTraceManager()).thenReturn(mMockTraceManager); when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager); when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler); when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L); + when(mMockAms.getTraceManager()).thenReturn(mMockTraceManager); initMockWindowManager(); mFullScreenMagnificationController = new FullScreenMagnificationController( @@ -773,20 +777,20 @@ public class FullScreenMagnificationControllerTest { } @Test - public void testRotation_resetsMagnification() { + public void testDisplaySizeChanged_resetsMagnification() { for (int i = 0; i < DISPLAY_COUNT; i++) { - rotation_resetsMagnification(i); + changeDisplaySize_resetsMagnification(i); resetMockWindowManager(); } } - private void rotation_resetsMagnification(int displayId) { + private void changeDisplaySize_resetsMagnification(int displayId) { register(displayId); MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId); zoomIn2xToMiddle(displayId); mMessageCapturingHandler.sendAllMessages(); assertTrue(mFullScreenMagnificationController.isMagnifying(displayId)); - callbacks.onRotationChanged(0); + callbacks.onDisplaySizeChanged(); mMessageCapturingHandler.sendAllMessages(); assertFalse(mFullScreenMagnificationController.isMagnifying(displayId)); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index f881f04e5b07..b14c353397e2 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -52,6 +52,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.AccessibilityManagerService; +import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.EventStreamTransformation; import com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback; import com.android.server.testutils.OffsettableClock; @@ -129,6 +130,10 @@ public class FullScreenMagnificationGestureHandlerTest { MagnificationInfoChangedCallback mMagnificationInfoChangedCallback; @Mock WindowMagnificationPromptController mWindowMagnificationPromptController; + @Mock + AccessibilityManagerService mMockAccessibilityManagerService; + @Mock + AccessibilityTraceManager mMockTraceManager; private OffsettableClock mClock; private FullScreenMagnificationGestureHandler mMgh; @@ -144,7 +149,9 @@ public class FullScreenMagnificationGestureHandlerTest { mock(FullScreenMagnificationController.ControllerContext.class); final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class); when(mockController.getContext()).thenReturn(mContext); - when(mockController.getAms()).thenReturn(mock(AccessibilityManagerService.class)); + when(mockController.getAms()).thenReturn(mMockAccessibilityManagerService); + when(mMockAccessibilityManagerService.getTraceManager()).thenReturn(mMockTraceManager); + when(mockController.getTraceManager()).thenReturn(mMockTraceManager); when(mockController.getWindowManager()).thenReturn(mockWindowManager); when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper())); when(mockController.newValueAnimator()).thenReturn(new ValueAnimator()); @@ -179,7 +186,7 @@ public class FullScreenMagnificationGestureHandlerTest { private FullScreenMagnificationGestureHandler newInstance(boolean detectTripleTap, boolean detectShortcutTrigger) { FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler( - mContext, mFullScreenMagnificationController, mMockCallback, + mContext, mFullScreenMagnificationController, mMockTraceManager, mMockCallback, detectTripleTap, detectShortcutTrigger, mWindowMagnificationPromptController, DISPLAY_0); mHandler = new TestHandler(h.mDetectingState, mClock) { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index b7f5f4da9d9d..e82adc8b403b 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -54,6 +54,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.accessibility.AccessibilityManagerService; +import com.android.server.accessibility.AccessibilityTraceManager; import org.junit.After; import org.junit.Before; @@ -84,6 +85,8 @@ public class MagnificationControllerTest { Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; @Mock + private AccessibilityTraceManager mTraceManager; + @Mock private AccessibilityManagerService mService; @Mock private MagnificationController.TransitionCallBack mTransitionCallBack; @@ -112,7 +115,7 @@ public class MagnificationControllerTest { CURRENT_USER_ID); mWindowMagnificationManager = Mockito.spy( new WindowMagnificationManager(mContext, CURRENT_USER_ID, - mock(WindowMagnificationManager.Callback.class))); + mock(WindowMagnificationManager.Callback.class), mTraceManager)); mMockConnection = new MockWindowMagnificationConnection(true); mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mScreenMagnificationControllerStubber = new FullScreenMagnificationControllerStubber( diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java index 514d16a0149c..ef6ed88011ef 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java @@ -31,6 +31,8 @@ import android.view.MotionEvent; import androidx.test.runner.AndroidJUnit4; +import com.android.server.accessibility.AccessibilityTraceManager; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,6 +51,8 @@ public class MagnificationGestureHandlerTest { Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; @Mock + AccessibilityTraceManager mTraceManager; + @Mock MagnificationGestureHandler.Callback mCallback; @Before @@ -57,6 +61,7 @@ public class MagnificationGestureHandlerTest { mMgh = new TestMagnificationGestureHandler(DISPLAY_0, /* detectTripleTap= */true, /* detectShortcutTrigger= */true, + mTraceManager, mCallback); } @@ -129,8 +134,9 @@ public class MagnificationGestureHandlerTest { boolean mIsInternalMethodCalled = false; TestMagnificationGestureHandler(int displayId, boolean detectTripleTap, - boolean detectShortcutTrigger, @NonNull Callback callback) { - super(displayId, detectTripleTap, detectShortcutTrigger, callback); + boolean detectShortcutTrigger, @NonNull AccessibilityTraceManager trace, + @NonNull Callback callback) { + super(displayId, detectTripleTap, detectShortcutTrigger, trace, callback); } @Override diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java index c88bc3b2e15b..1638563e8242 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java @@ -29,6 +29,8 @@ import android.view.accessibility.IWindowMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; import android.view.accessibility.MagnificationAnimationCallback; +import com.android.server.accessibility.AccessibilityTraceManager; + import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -45,6 +47,8 @@ public class WindowMagnificationConnectionWrapperTest { private IWindowMagnificationConnection mConnection; @Mock + private AccessibilityTraceManager mTrace; + @Mock private IWindowMagnificationConnectionCallback mCallback; @Mock private MagnificationAnimationCallback mAnimationCallback; @@ -57,7 +61,7 @@ public class WindowMagnificationConnectionWrapperTest { MockitoAnnotations.initMocks(this); mMockWindowMagnificationConnection = new MockWindowMagnificationConnection(); mConnection = mMockWindowMagnificationConnection.getConnection(); - mConnectionWrapper = new WindowMagnificationConnectionWrapper(mConnection); + mConnectionWrapper = new WindowMagnificationConnectionWrapper(mConnection, mTrace); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java index b9498d641ed7..6a5aae672881 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java @@ -35,6 +35,7 @@ import android.view.ViewConfiguration; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.EventStreamTransformation; import com.android.server.accessibility.utils.TouchEventGenerator; @@ -74,16 +75,18 @@ public class WindowMagnificationGestureHandlerTest { private WindowMagnificationGestureHandler mWindowMagnificationGestureHandler; @Mock MagnificationGestureHandler.Callback mMockCallback; + @Mock + AccessibilityTraceManager mMockTrace; @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); mContext = InstrumentationRegistry.getInstrumentation().getContext(); mWindowMagnificationManager = new WindowMagnificationManager(mContext, 0, - mock(WindowMagnificationManager.Callback.class)); + mock(WindowMagnificationManager.Callback.class), mMockTrace); mMockConnection = new MockWindowMagnificationConnection(); mWindowMagnificationGestureHandler = new WindowMagnificationGestureHandler( - mContext, mWindowMagnificationManager, mMockCallback, + mContext, mWindowMagnificationManager, mMockTrace, mMockCallback, /** detectTripleTap= */true, /** detectShortcutTrigger= */true, DISPLAY_0); mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationGestureHandler.setNext(strictMock(EventStreamTransformation.class)); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java index a20272ab438d..af6d40f2fdf2 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java @@ -52,6 +52,7 @@ import android.view.accessibility.MagnificationAnimationCallback; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.LocalServices; +import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.statusbar.StatusBarManagerInternal; import org.junit.Before; @@ -72,6 +73,8 @@ public class WindowMagnificationManagerTest { @Mock private Context mContext; @Mock + private AccessibilityTraceManager mMockTrace; + @Mock private StatusBarManagerInternal mMockStatusBarManagerInternal; @Mock private MagnificationAnimationCallback mAnimationCallback; @@ -88,7 +91,7 @@ public class WindowMagnificationManagerTest { mResolver = new MockContentResolver(); mMockConnection = new MockWindowMagnificationConnection(); mWindowMagnificationManager = new WindowMagnificationManager(mContext, CURRENT_USER_ID, - mMockCallback); + mMockCallback, mMockTrace); when(mContext.getContentResolver()).thenReturn(mResolver); doAnswer((InvocationOnMock invocation) -> { diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java index 4a67ec71fcaa..6faa7e7c89e7 100644 --- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java @@ -74,15 +74,20 @@ public class BatteryExternalStatsWorkerTest { @Test public void testTargetedEnergyConsumerQuerying() { final int numCpuClusters = 4; + final int numDisplays = 5; final int numOther = 3; // Add some energy consumers used by BatteryExternalStatsWorker. final IntArray tempAllIds = new IntArray(); - final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0, - "display"); - tempAllIds.add(displayId); - mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345); + final int[] displayIds = new int[numDisplays]; + for (int i = 0; i < numDisplays; i++) { + displayIds[i] = mPowerStatsInternal.addEnergyConsumer( + EnergyConsumerType.DISPLAY, i, "display" + i); + tempAllIds.add(displayIds[i]); + mPowerStatsInternal.incrementEnergyConsumption(displayIds[i], 12345 + i); + } + Arrays.sort(displayIds); final int wifiId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.WIFI, 0, "wifi"); @@ -130,9 +135,13 @@ public class BatteryExternalStatsWorkerTest { final EnergyConsumerResult[] displayResults = mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_DISPLAY).getNow(null); - // Results should only have the display energy consumer - assertEquals(1, displayResults.length); - assertEquals(displayId, displayResults[0].id); + // Results should only have the cpu cluster energy consumers + final int[] receivedDisplayIds = new int[displayResults.length]; + for (int i = 0; i < displayResults.length; i++) { + receivedDisplayIds[i] = displayResults[i].id; + } + Arrays.sort(receivedDisplayIds); + assertArrayEquals(displayIds, receivedDisplayIds); final EnergyConsumerResult[] wifiResults = mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_WIFI).getNow(null); @@ -193,6 +202,7 @@ public class BatteryExternalStatsWorkerTest { public class TestBatteryStatsImpl extends BatteryStatsImpl { public TestBatteryStatsImpl(Context context) { mPowerProfile = new PowerProfile(context, true /* forTest */); + initTimersAndCounters(); } } diff --git a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java index 8c87506295f3..a0cbcadee844 100644 --- a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java +++ b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java @@ -16,8 +16,6 @@ package com.android.server.am; -import static com.android.server.am.MeasuredEnergySnapshot.UNAVAILABLE; - import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; @@ -120,7 +118,7 @@ public final class MeasuredEnergySnapshotTest { // results0 MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0); if (delta != null) { // null is fine here. If non-null, it better be uninteresting though. - assertEquals(UNAVAILABLE, delta.displayChargeUC); + assertNull(delta.displayChargeUC); assertNull(delta.otherTotalChargeUC); assertNull(delta.otherUidChargesUC); } @@ -130,7 +128,7 @@ public final class MeasuredEnergySnapshotTest { assertNotNull(delta); long expectedChargeUC; expectedChargeUC = calculateChargeConsumedUC(14_000, VOLTAGE_0, 24_000, VOLTAGE_1); - assertEquals(expectedChargeUC, delta.displayChargeUC); + assertEquals(expectedChargeUC, delta.displayChargeUC[0]); assertNotNull(delta.otherTotalChargeUC); @@ -149,14 +147,14 @@ public final class MeasuredEnergySnapshotTest { delta = snapshot.updateAndGetDelta(RESULTS_2, VOLTAGE_2); assertNotNull(delta); expectedChargeUC = calculateChargeConsumedUC(24_000, VOLTAGE_1, 36_000, VOLTAGE_2); - assertEquals(expectedChargeUC, delta.displayChargeUC); + assertEquals(expectedChargeUC, delta.displayChargeUC[0]); assertNull(delta.otherUidChargesUC); assertNull(delta.otherTotalChargeUC); // results3 delta = snapshot.updateAndGetDelta(RESULTS_3, VOLTAGE_3); assertNotNull(delta); - assertEquals(UNAVAILABLE, delta.displayChargeUC); + assertNull(delta.displayChargeUC); assertNotNull(delta.otherTotalChargeUC); @@ -183,7 +181,7 @@ public final class MeasuredEnergySnapshotTest { delta = snapshot.updateAndGetDelta(RESULTS_4, VOLTAGE_4); assertNotNull(delta); expectedChargeUC = calculateChargeConsumedUC(36_000, VOLTAGE_2, 43_000, VOLTAGE_4); - assertEquals(expectedChargeUC, delta.displayChargeUC); + assertEquals(expectedChargeUC, delta.displayChargeUC[0]); assertNotNull(delta.otherTotalChargeUC); expectedChargeUC = calculateChargeConsumedUC(190_000, VOLTAGE_3, 290_000, VOLTAGE_4); @@ -210,7 +208,7 @@ public final class MeasuredEnergySnapshotTest { // results0 MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0); if (delta != null) { // null is fine here. If non-null, it better be uninteresting though. - assertEquals(UNAVAILABLE, delta.displayChargeUC); + assertNull(delta.displayChargeUC); assertNull(delta.otherTotalChargeUC); assertNull(delta.otherUidChargesUC); } @@ -220,7 +218,7 @@ public final class MeasuredEnergySnapshotTest { assertNotNull(delta); final long expectedChargeUC = calculateChargeConsumedUC(14_000, VOLTAGE_0, 24_000, VOLTAGE_1); - assertEquals(expectedChargeUC, delta.displayChargeUC); + assertEquals(expectedChargeUC, delta.displayChargeUC[0]); assertNull(delta.otherTotalChargeUC); // Although in the results, they're not in the idMap assertNull(delta.otherUidChargesUC); } diff --git a/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java b/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java index 10f4c05eb6d8..e6a8dea973c9 100644 --- a/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -26,7 +27,9 @@ import android.app.Instrumentation; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; +import android.os.IBinder; import android.os.SystemClock; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; @@ -42,6 +45,8 @@ import org.junit.runner.RunWith; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Build/Install/Run: @@ -69,6 +74,12 @@ public final class ServiceRestarterTest { private static final int ACTION_STOPPKG = 8; private static final int ACTION_ALL = ACTION_START | ACTION_KILL | ACTION_WAIT | ACTION_STOPPKG; + private static final String LOCAL_APK_BASE_PATH = "/data/local/tmp/cts/content/"; + private static final String TEST_PACKAGE3_APK = "SimpleServiceTestApp3.apk"; + private static final String ACTION_SERVICE_WITH_DEP_PKG = + "com.android.servicestests.apps.simpleservicetestapp.ACTION_SERVICE_WITH_DEP_PKG"; + private static final String EXTRA_TARGET_PACKAGE = "target_package"; + private Context mContext; private Instrumentation mInstrumentation; private int mTestPackage1Uid; @@ -199,6 +210,83 @@ public final class ServiceRestarterTest { return res; } + @Test + public void testServiceWithDepPkgStopped() throws Exception { + final CountDownLatch[] latchHolder = new CountDownLatch[1]; + final ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + latchHolder[0].countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + latchHolder[0].countDown(); + } + }; + + final long timeout = 5_000; + final long shortTimeout = 2_000; + final Intent intent = new Intent(ACTION_SERVICE_WITH_DEP_PKG); + final String testPkg = TEST_PACKAGE2_NAME; + final String libPkg = TEST_PACKAGE3_NAME; + final String apkPath = LOCAL_APK_BASE_PATH + TEST_PACKAGE3_APK; + final ActivityManager am = mContext.getSystemService(ActivityManager.class); + + intent.setComponent(ComponentName.unflattenFromString(testPkg + "/" + TEST_SERVICE_NAME)); + intent.putExtra(EXTRA_TARGET_PACKAGE, libPkg); + try { + executeShellCmd("am service-restart-backoff disable " + testPkg); + + latchHolder[0] = new CountDownLatch(1); + assertTrue("Unable to bind to test service in " + testPkg, + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)); + assertTrue("Timed out to bind service in " + testPkg, + latchHolder[0].await(timeout, TimeUnit.MILLISECONDS)); + + Thread.sleep(shortTimeout); + assertTrue(libPkg + " should be a dependency package of " + testPkg, + isPackageDependency(testPkg, libPkg)); + + // Force-stop lib package, the test service should be restarted. + latchHolder[0] = new CountDownLatch(2); + am.forceStopPackage(libPkg); + assertTrue("Test service in didn't restart in " + testPkg, + latchHolder[0].await(timeout, TimeUnit.MILLISECONDS)); + + Thread.sleep(shortTimeout); + + // Re-install the lib package, the test service should be restarted. + latchHolder[0] = new CountDownLatch(2); + assertTrue("Unable to install package " + apkPath, installPackage(apkPath)); + assertTrue("Test service in didn't restart in " + testPkg, + latchHolder[0].await(timeout, TimeUnit.MILLISECONDS)); + + Thread.sleep(shortTimeout); + + // Force-stop the service package, the test service should not be restarted. + latchHolder[0] = new CountDownLatch(2); + am.forceStopPackage(testPkg); + assertFalse("Test service should not be restarted in " + testPkg, + latchHolder[0].await(timeout * 2, TimeUnit.MILLISECONDS)); + } finally { + executeShellCmd("am service-restart-backoff enable " + testPkg); + mContext.unbindService(conn); + am.forceStopPackage(testPkg); + } + } + + private boolean isPackageDependency(String pkgName, String libPackage) throws Exception { + final String output = SystemUtil.runShellCommand("dumpsys activity processes " + pkgName); + final Matcher matcher = Pattern.compile("packageDependencies=\\{.*?\\b" + + libPackage + "\\b.*?\\}").matcher(output); + return matcher.find(); + } + + private boolean installPackage(String apkPath) throws Exception { + return executeShellCmd("pm install -t " + apkPath).equals("Success\n"); + } + private void startServiceAndWait(String pkgName, MyUidImportanceListener uidListener, long timeout) throws Exception { final Intent intent = new Intent(); diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java index 5c7a580a2593..1c49e6e64dd8 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -146,12 +146,15 @@ public final class AppHibernationServiceTest { } @Test - public void testSetHibernatingForUser_packageIsHibernating() { + public void testSetHibernatingForUser_packageIsHibernating() throws Exception { // WHEN we hibernate a package for a user mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); // THEN the package is marked hibernating for the user assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_1)); + verify(mIActivityManager).forceStopPackage(PACKAGE_NAME_1, USER_ID_1); + verify(mIPackageManager).deleteApplicationCacheFilesAsUser( + eq(PACKAGE_NAME_1), eq(USER_ID_1), any()); } @Test @@ -204,6 +207,7 @@ public final class AppHibernationServiceTest { // THEN the package is marked hibernating for the user assertTrue(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1)); + verify(mPackageManagerInternal).deleteOatArtifactsOfPackage(PACKAGE_NAME_1); } @Test diff --git a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java index 0d475c00569e..91bf4d12a299 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java @@ -135,7 +135,8 @@ public class AppSearchImplPlatformTest { "schema1", ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), /*forceOverride=*/ false, - /*schemaVersion=*/ 0); + /*schemaVersion=*/ 0, + /*setSchemaStatsBuilder=*/ null); // "schema1" is platform hidden now and package visible to package1 assertThat(mVisibilityStore.isSchemaSearchableByCaller( @@ -167,7 +168,8 @@ public class AppSearchImplPlatformTest { "schema1", ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), /*forceOverride=*/ false, - /*schemaVersion=*/ 0); + /*schemaVersion=*/ 0, + /*setSchemaStatsBuilder=*/ null); // Check that "schema1" still has the same visibility settings SystemUtil.runWithShellPermissionIdentity(() -> assertThat( @@ -241,7 +243,8 @@ public class AppSearchImplPlatformTest { "schema1", ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), /*forceOverride=*/ false, - /*schemaVersion=*/ 0); + /*schemaVersion=*/ 0, + /*setSchemaStatsBuilder=*/ null); // "schema1" is platform hidden now and package accessible assertThat(mVisibilityStore.isSchemaSearchableByCaller( @@ -269,7 +272,8 @@ public class AppSearchImplPlatformTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ true, - /*schemaVersion=*/ 0); + /*schemaVersion=*/ 0, + /*setSchemaStatsBuilder=*/ null); // Check that "schema1" is no longer considered platform hidden or package accessible assertThat(mVisibilityStore.isSchemaSearchableByCaller( @@ -298,7 +302,8 @@ public class AppSearchImplPlatformTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*schemaVersion=*/ 0); + /*schemaVersion=*/ 0, + /*setSchemaStatsBuilder=*/ null); assertThat(mVisibilityStore.isSchemaSearchableByCaller( "package", @@ -333,7 +338,8 @@ public class AppSearchImplPlatformTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*schemaVersion=*/ 0); + /*schemaVersion=*/ 0, + /*setSchemaStatsBuilder=*/ null); assertThat(mVisibilityStore.isSchemaSearchableByCaller( "package", @@ -361,7 +367,8 @@ public class AppSearchImplPlatformTest { /*schemasNotDisplayedBySystem=*/ Collections.singletonList("Schema"), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*schemaVersion=*/ 0); + /*schemaVersion=*/ 0, + /*setSchemaStatsBuilder=*/ null); assertThat(mVisibilityStore.isSchemaSearchableByCaller( "package", @@ -390,7 +397,8 @@ public class AppSearchImplPlatformTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*schemaVersion=*/ 0); + /*schemaVersion=*/ 0, + /*setSchemaStatsBuilder=*/ null); assertThat(mVisibilityStore .isSchemaSearchableByCaller( "package", @@ -431,7 +439,8 @@ public class AppSearchImplPlatformTest { "Schema", ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), /*forceOverride=*/ false, - /*schemaVersion=*/ 0); + /*schemaVersion=*/ 0, + /*setSchemaStatsBuilder=*/ null); assertThat(mVisibilityStore .isSchemaSearchableByCaller( "package", diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java index f40a5ad7bcb6..dd3b3ec08dbf 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java @@ -76,13 +76,14 @@ import java.util.Map; import java.util.Set; public class AppSearchImplTest { - @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); - private AppSearchImpl mAppSearchImpl; /** * Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class. */ private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true; + @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + private AppSearchImpl mAppSearchImpl; + @Before public void setUp() throws Exception { mAppSearchImpl = @@ -439,7 +440,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert a document and then remove it to generate garbage. GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type").build(); @@ -499,7 +501,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert a valid doc GenericDocument validDoc = @@ -591,7 +594,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert a valid doc appSearchImpl.putDocument( @@ -626,7 +630,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert document GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type").build(); @@ -660,7 +665,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); mAppSearchImpl.setSchema( "package", "database2", @@ -669,7 +675,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert documents GenericDocument document1 = @@ -714,7 +721,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert document GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type").build(); @@ -756,7 +764,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert package2 schema List<AppSearchSchema> schema2 = @@ -769,7 +778,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert package1 document GenericDocument document = @@ -812,7 +822,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert package2 schema List<AppSearchSchema> schema2 = @@ -825,7 +836,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert package1 document GenericDocument document = @@ -889,7 +901,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert two package1 documents GenericDocument document1 = @@ -914,7 +927,8 @@ public class AppSearchImplTest { assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2); long nextPageToken = searchResultPage.getNextPageToken(); - searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken); + searchResultPage = + mAppSearchImpl.getNextPage("package1", nextPageToken, /*statsBuilder=*/ null); assertThat(searchResultPage.getResults()).hasSize(1); assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1); } @@ -932,7 +946,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert two package1 documents GenericDocument document1 = @@ -962,14 +977,17 @@ public class AppSearchImplTest { AppSearchException e = assertThrows( AppSearchException.class, - () -> mAppSearchImpl.getNextPage("package2", nextPageToken)); + () -> + mAppSearchImpl.getNextPage( + "package2", nextPageToken, /*statsBuilder=*/ null)); assertThat(e) .hasMessageThat() .contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken); assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR); // Can continue getting next page for package1 - searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken); + searchResultPage = + mAppSearchImpl.getNextPage("package1", nextPageToken, /*statsBuilder=*/ null); assertThat(searchResultPage.getResults()).hasSize(1); assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1); } @@ -987,7 +1005,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert two package1 documents GenericDocument document1 = @@ -1019,7 +1038,8 @@ public class AppSearchImplTest { assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2); long nextPageToken = searchResultPage.getNextPageToken(); - searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken); + searchResultPage = + mAppSearchImpl.getNextPage("package1", nextPageToken, /*statsBuilder=*/ null); assertThat(searchResultPage.getResults()).hasSize(1); assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1); } @@ -1037,7 +1057,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert two package1 documents GenericDocument document1 = @@ -1074,14 +1095,17 @@ public class AppSearchImplTest { AppSearchException e = assertThrows( AppSearchException.class, - () -> mAppSearchImpl.getNextPage("package2", nextPageToken)); + () -> + mAppSearchImpl.getNextPage( + "package2", nextPageToken, /*statsBuilder=*/ null)); assertThat(e) .hasMessageThat() .contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken); assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR); // Can continue getting next page for package1 - searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken); + searchResultPage = + mAppSearchImpl.getNextPage("package1", nextPageToken, /*statsBuilder=*/ null); assertThat(searchResultPage.getResults()).hasSize(1); assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1); } @@ -1099,7 +1123,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert two package1 documents GenericDocument document1 = @@ -1132,7 +1157,9 @@ public class AppSearchImplTest { AppSearchException e = assertThrows( AppSearchException.class, - () -> mAppSearchImpl.getNextPage("package1", nextPageToken)); + () -> + mAppSearchImpl.getNextPage( + "package1", nextPageToken, /*statsBuilder=*/ null)); assertThat(e) .hasMessageThat() .contains("Package \"package1\" cannot use nextPageToken: " + nextPageToken); @@ -1152,7 +1179,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert two package1 documents GenericDocument document1 = @@ -1189,7 +1217,8 @@ public class AppSearchImplTest { assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR); // Can continue getting next page for package1 - searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken); + searchResultPage = + mAppSearchImpl.getNextPage("package1", nextPageToken, /*statsBuilder=*/ null); assertThat(searchResultPage.getResults()).hasSize(1); assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1); } @@ -1207,7 +1236,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert two package1 documents GenericDocument document1 = @@ -1247,7 +1277,9 @@ public class AppSearchImplTest { AppSearchException e = assertThrows( AppSearchException.class, - () -> mAppSearchImpl.getNextPage("package1", nextPageToken)); + () -> + mAppSearchImpl.getNextPage( + "package1", nextPageToken, /*statsBuilder=*/ null)); assertThat(e) .hasMessageThat() .contains("Package \"package1\" cannot use nextPageToken: " + nextPageToken); @@ -1267,7 +1299,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert two package1 documents GenericDocument document1 = @@ -1311,7 +1344,8 @@ public class AppSearchImplTest { assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR); // Can continue getting next page for package1 - searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken); + searchResultPage = + mAppSearchImpl.getNextPage("package1", nextPageToken, /*statsBuilder=*/ null); assertThat(searchResultPage.getResults()).hasSize(1); assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1); } @@ -1355,7 +1389,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Create expected schemaType proto. SchemaProto expectedProto = @@ -1400,7 +1435,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Create incompatible schema List<AppSearchSchema> newSchemas = @@ -1416,7 +1452,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ true, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Text"); assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("Email"); } @@ -1439,7 +1476,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Create expected schemaType proto. SchemaProto expectedProto = @@ -1472,8 +1510,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); - + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Check the Document type has been deleted. assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Document"); @@ -1486,7 +1524,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ true, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Check Document schema is removed. expectedProto = @@ -1524,7 +1563,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); mAppSearchImpl.setSchema( "package", "database2", @@ -1533,7 +1573,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Create expected schemaType proto. SchemaProto expectedProto = @@ -1573,7 +1614,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ true, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Create expected schemaType list, database 1 should only contain Email but database 2 // remains in same. @@ -1618,7 +1660,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert package document GenericDocument document = @@ -1680,7 +1723,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); mAppSearchImpl.setSchema( "packageB", "database", @@ -1689,7 +1733,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Verify these two packages is stored in AppSearch SchemaProto expectedProto = @@ -1735,7 +1780,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); assertThat(mAppSearchImpl.getPackageToDatabases()) .containsExactlyEntriesIn(expectedMapping); @@ -1749,7 +1795,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); assertThat(mAppSearchImpl.getPackageToDatabases()) .containsExactlyEntriesIn(expectedMapping); @@ -1763,7 +1810,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); assertThat(mAppSearchImpl.getPackageToDatabases()) .containsExactlyEntriesIn(expectedMapping); } @@ -1822,7 +1870,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert two docs GenericDocument document1 = @@ -1973,7 +2022,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Since "package1" doesn't have a document, it get any space attributed to it. StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("package1"); @@ -1996,7 +2046,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert document for "package1" GenericDocument document = @@ -2012,7 +2063,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert two documents for "package2" document = new GenericDocument.Builder<>("namespace", "id1", "type").build(); @@ -2061,7 +2113,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // "package2" doesn't exist yet, so it shouldn't have any storage size StorageInfo storageInfo = @@ -2084,7 +2137,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Since "package1", "database1" doesn't have a document, it get any space attributed to it. StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database1"); @@ -2106,7 +2160,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); mAppSearchImpl.setSchema( "package1", "database2", @@ -2115,7 +2170,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Add a document for "package1", "database1" GenericDocument document = @@ -2165,7 +2221,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); appSearchImpl.close(); @@ -2181,7 +2238,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0)); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null)); assertThrows( IllegalStateException.class, () -> appSearchImpl.getSchema("package", "database")); @@ -2225,7 +2283,9 @@ public class AppSearchImplTest { assertThrows( IllegalStateException.class, - () -> appSearchImpl.getNextPage("package", /*nextPageToken=*/ 1L)); + () -> + appSearchImpl.getNextPage( + "package", /*nextPageToken=*/ 1L, /*statsBuilder=*/ null)); assertThrows( IllegalStateException.class, @@ -2296,7 +2356,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Add a document and persist it. GenericDocument document = @@ -2343,7 +2404,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Add two documents and persist them. GenericDocument document1 = @@ -2423,7 +2485,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Add two documents and persist them. GenericDocument document1 = @@ -2511,7 +2574,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Add two documents GenericDocument document1 = @@ -2562,7 +2626,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert a document which is too large GenericDocument document = @@ -2636,7 +2701,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Index a document mAppSearchImpl.putDocument( @@ -2723,7 +2789,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Index 3 documents mAppSearchImpl.putDocument( @@ -2836,7 +2903,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); mAppSearchImpl.setSchema( "package1", "database2", @@ -2845,7 +2913,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); mAppSearchImpl.setSchema( "package2", "database1", @@ -2854,7 +2923,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); mAppSearchImpl.setSchema( "package2", "database2", @@ -2863,7 +2933,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Index documents in package1/database1 mAppSearchImpl.putDocument( @@ -3002,7 +3073,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Index 3 documents mAppSearchImpl.putDocument( @@ -3131,7 +3203,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Index a document mAppSearchImpl.putDocument( @@ -3210,7 +3283,8 @@ public class AppSearchImplTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Index a document mAppSearchImpl.putDocument( diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java index 7c976876a731..2ab5fd554675 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java @@ -33,6 +33,7 @@ import com.android.server.appsearch.external.localstorage.stats.OptimizeStats; import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats; import com.android.server.appsearch.external.localstorage.stats.RemoveStats; import com.android.server.appsearch.external.localstorage.stats.SearchStats; +import com.android.server.appsearch.external.localstorage.stats.SetSchemaStats; import com.android.server.appsearch.icing.proto.DeleteStatsProto; import com.android.server.appsearch.icing.proto.DocumentProto; import com.android.server.appsearch.icing.proto.InitializeStatsProto; @@ -41,6 +42,7 @@ import com.android.server.appsearch.icing.proto.PutDocumentStatsProto; import com.android.server.appsearch.icing.proto.PutResultProto; import com.android.server.appsearch.icing.proto.QueryStatsProto; import com.android.server.appsearch.icing.proto.ScoringSpecProto; +import com.android.server.appsearch.icing.proto.SetSchemaResultProto; import com.android.server.appsearch.icing.proto.StatusProto; import com.android.server.appsearch.icing.proto.TermMatchType; @@ -57,14 +59,17 @@ import java.util.Collections; import java.util.List; public class AppSearchLoggerTest { - @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); - private AppSearchImpl mAppSearchImpl; - private TestLogger mLogger; + private static final String PACKAGE_NAME = "packageName"; + private static final String DATABASE = "database"; /** * Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class. */ private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true; + @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + private AppSearchImpl mAppSearchImpl; + private TestLogger mLogger; + @Before public void setUp() throws Exception { mAppSearchImpl = @@ -84,6 +89,7 @@ public class AppSearchLoggerTest { @Nullable SearchStats mSearchStats; @Nullable RemoveStats mRemoveStats; @Nullable OptimizeStats mOptimizeStats; + @Nullable SetSchemaStats mSetSchemaStats; @Override public void logStats(@NonNull CallStats stats) { @@ -114,6 +120,11 @@ public class AppSearchLoggerTest { public void logStats(@NonNull OptimizeStats stats) { mOptimizeStats = stats; } + + @Override + public void logStats(@NonNull SetSchemaStats stats) { + mSetSchemaStats = stats; + } } @Test @@ -194,7 +205,7 @@ public class AppSearchLoggerTest { .setExceededMaxTokenNum(nativeExceededMaxNumTokens) .build()) .build(); - PutDocumentStats.Builder pBuilder = new PutDocumentStats.Builder("packageName", "database"); + PutDocumentStats.Builder pBuilder = new PutDocumentStats.Builder(PACKAGE_NAME, DATABASE); AppSearchLoggerHelper.copyNativeStats(nativePutDocumentStats, pBuilder); @@ -248,8 +259,8 @@ public class AppSearchLoggerTest { .setDocumentRetrievalLatencyMs(nativeDocumentRetrievingLatencyMillis) .build(); SearchStats.Builder qBuilder = - new SearchStats.Builder(SearchStats.VISIBILITY_SCOPE_LOCAL, "packageName") - .setDatabase("database"); + new SearchStats.Builder(SearchStats.VISIBILITY_SCOPE_LOCAL, PACKAGE_NAME) + .setDatabase(DATABASE); AppSearchLoggerHelper.copyNativeStats(nativeQueryStats, qBuilder); @@ -336,6 +347,35 @@ public class AppSearchLoggerTest { .isEqualTo(nativeTimeSinceLastOptimizeMillis); } + @Test + public void testAppSearchLoggerHelper_testCopyNativeStats_setSchema() { + ImmutableList<String> newSchemaTypeChangeList = ImmutableList.of("new1"); + ImmutableList<String> deletedSchemaTypesList = ImmutableList.of("deleted1", "deleted2"); + ImmutableList<String> compatibleTypesList = ImmutableList.of("compatible1", "compatible2"); + ImmutableList<String> indexIncompatibleTypeChangeList = ImmutableList.of("index1"); + ImmutableList<String> backwardsIncompatibleTypeChangeList = ImmutableList.of("backwards1"); + SetSchemaResultProto setSchemaResultProto = + SetSchemaResultProto.newBuilder() + .addAllNewSchemaTypes(newSchemaTypeChangeList) + .addAllDeletedSchemaTypes(deletedSchemaTypesList) + .addAllFullyCompatibleChangedSchemaTypes(compatibleTypesList) + .addAllIndexIncompatibleChangedSchemaTypes(indexIncompatibleTypeChangeList) + .addAllIncompatibleSchemaTypes(backwardsIncompatibleTypeChangeList) + .build(); + SetSchemaStats.Builder sBuilder = new SetSchemaStats.Builder(PACKAGE_NAME, DATABASE); + + AppSearchLoggerHelper.copyNativeStats(setSchemaResultProto, sBuilder); + + SetSchemaStats sStats = sBuilder.build(); + assertThat(sStats.getNewTypeCount()).isEqualTo(newSchemaTypeChangeList.size()); + assertThat(sStats.getDeletedTypeCount()).isEqualTo(deletedSchemaTypesList.size()); + assertThat(sStats.getCompatibleTypeChangeCount()).isEqualTo(compatibleTypesList.size()); + assertThat(sStats.getIndexIncompatibleTypeChangeCount()) + .isEqualTo(indexIncompatibleTypeChangeList.size()); + assertThat(sStats.getBackwardsIncompatibleTypeChangeCount()) + .isEqualTo(backwardsIncompatibleTypeChangeList.size()); + } + // // Testing actual logging // @@ -388,7 +428,8 @@ public class AppSearchLoggerTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); GenericDocument doc1 = new GenericDocument.Builder<>("namespace", "id1", "Type1").build(); GenericDocument doc2 = new GenericDocument.Builder<>("namespace", "id2", "Type1").build(); appSearchImpl.putDocument(testPackageName, testDatabase, doc1, mLogger); @@ -439,7 +480,8 @@ public class AppSearchLoggerTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); // Insert a valid doc GenericDocument doc1 = new GenericDocument.Builder<>("namespace", "id1", "Type1").build(); @@ -495,7 +537,8 @@ public class AppSearchLoggerTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type") @@ -542,7 +585,8 @@ public class AppSearchLoggerTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type") @@ -592,7 +636,8 @@ public class AppSearchLoggerTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); GenericDocument document1 = new GenericDocument.Builder<>("namespace", "id1", "type") .setPropertyString("subject", "testPut example1") @@ -661,7 +706,8 @@ public class AppSearchLoggerTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); SearchSpec searchSpec = new SearchSpec.Builder() @@ -701,7 +747,8 @@ public class AppSearchLoggerTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); GenericDocument document = new GenericDocument.Builder<>(testNamespace, testId, "type").build(); mAppSearchImpl.putDocument(testPackageName, testDatabase, document, /*logger=*/ null); @@ -735,7 +782,8 @@ public class AppSearchLoggerTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); GenericDocument document = new GenericDocument.Builder<>(testNamespace, testId, "type").build(); @@ -780,7 +828,8 @@ public class AppSearchLoggerTest { /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), /*schemasVisibleToPackages=*/ Collections.emptyMap(), /*forceOverride=*/ false, - /*version=*/ 0); + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); GenericDocument document1 = new GenericDocument.Builder<>(testNamespace, "id1", "type").build(); GenericDocument document2 = @@ -800,7 +849,58 @@ public class AppSearchLoggerTest { assertThat(rStats.getDatabase()).isEqualTo(testDatabase); assertThat(rStats.getStatusCode()).isEqualTo(AppSearchResult.RESULT_OK); // delete by query - assertThat(rStats.getDeleteType()).isEqualTo(DeleteStatsProto.DeleteType.Code.QUERY_VALUE); + assertThat(rStats.getDeleteType()) + .isEqualTo(DeleteStatsProto.DeleteType.Code.DEPRECATED_QUERY_VALUE); assertThat(rStats.getDeletedDocumentCount()).isEqualTo(2); } + + @Test + public void testLoggingStats_setSchema() throws Exception { + AppSearchSchema schema1 = + new AppSearchSchema.Builder("testSchema") + .addProperty( + new AppSearchSchema.StringPropertyConfig.Builder("subject") + .setCardinality( + AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED) + .setIndexingType( + AppSearchSchema.StringPropertyConfig + .INDEXING_TYPE_PREFIXES) + .setTokenizerType( + AppSearchSchema.StringPropertyConfig + .TOKENIZER_TYPE_PLAIN) + .build()) + .build(); + mAppSearchImpl.setSchema( + PACKAGE_NAME, + DATABASE, + Collections.singletonList(schema1), + /*visibilityStore=*/ null, + /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), + /*schemasVisibleToPackages=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0, + /* setSchemaStatsBuilder= */ null); + + // create a backwards incompatible schema + SetSchemaStats.Builder sStatsBuilder = new SetSchemaStats.Builder(PACKAGE_NAME, DATABASE); + AppSearchSchema schema2 = new AppSearchSchema.Builder("testSchema").build(); + mAppSearchImpl.setSchema( + PACKAGE_NAME, + DATABASE, + Collections.singletonList(schema2), + /*visibilityStore=*/ null, + /*schemasNotDisplayedBySystem=*/ Collections.emptyList(), + /*schemasVisibleToPackages=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0, + /* setSchemaStatsBuilder= */ sStatsBuilder); + + SetSchemaStats sStats = sStatsBuilder.build(); + assertThat(sStats.getPackageName()).isEqualTo(PACKAGE_NAME); + assertThat(sStats.getDatabase()).isEqualTo(DATABASE); + assertThat(sStats.getNewTypeCount()).isEqualTo(0); + assertThat(sStats.getCompatibleTypeChangeCount()).isEqualTo(0); + assertThat(sStats.getIndexIncompatibleTypeChangeCount()).isEqualTo(1); + assertThat(sStats.getBackwardsIncompatibleTypeChangeCount()).isEqualTo(1); + } } diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java index c1dc0e447c70..81aab416a9f9 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java @@ -264,17 +264,15 @@ public class AppSearchStatsTest { .setMigratedDocumentCount(6) .setSavedDocumentCount(7) .build(); - int nativeLatencyMillis = 1; - int newTypeCount = 2; - int compatibleTypeChangeCount = 3; - int indexIncompatibleTypeChangeCount = 4; - int backwardsIncompatibleTypeChangeCount = 5; + int newTypeCount = 1; + int compatibleTypeChangeCount = 2; + int indexIncompatibleTypeChangeCount = 3; + int backwardsIncompatibleTypeChangeCount = 4; SetSchemaStats sStats = new SetSchemaStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE) .setStatusCode(TEST_STATUS_CODE) .setSchemaMigrationStats(schemaMigrationStats) .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS) - .setNativeLatencyMillis(nativeLatencyMillis) .setNewTypeCount(newTypeCount) .setCompatibleTypeChangeCount(compatibleTypeChangeCount) .setIndexIncompatibleTypeChangeCount(indexIncompatibleTypeChangeCount) @@ -287,7 +285,6 @@ public class AppSearchStatsTest { assertThat(sStats.getStatusCode()).isEqualTo(TEST_STATUS_CODE); assertThat(sStats.getSchemaMigrationStats()).isEqualTo(schemaMigrationStats); assertThat(sStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS); - assertThat(sStats.getNativeLatencyMillis()).isEqualTo(nativeLatencyMillis); assertThat(sStats.getNewTypeCount()).isEqualTo(newTypeCount); assertThat(sStats.getCompatibleTypeChangeCount()).isEqualTo(compatibleTypeChangeCount); assertThat(sStats.getIndexIncompatibleTypeChangeCount()) diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index b3f7587df612..b255a35c512e 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -302,6 +302,65 @@ public class AuthSessionTest { testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null)); } + // TODO (b/208484275) : Enable these tests + // @Test + // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception { + // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class); + // when(manager + // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt())) + // .thenReturn(false); + // when(mContext.getSystemService(SensorPrivacyManager.class)) + // .thenReturn(manager); + // setupFace(1 /* id */, false /* confirmationAlwaysRequired */, + // mock(IBiometricAuthenticator.class)); + // final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG); + // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false); + // assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult()); + // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) { + // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState()); + // } + // } + + // @Test + // public void testPreAuth_cannotAuthAndPrivacyEnabled() throws Exception { + // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class); + // when(manager + // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt())) + // .thenReturn(true); + // when(mContext.getSystemService(SensorPrivacyManager.class)) + // .thenReturn(manager); + // setupFace(1 /* id */, false /* confirmationAlwaysRequired */, + // mock(IBiometricAuthenticator.class)); + // final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG); + // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false); + // assertEquals(BiometricManager.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED, + // preAuthInfo.getCanAuthenticateResult()); + // // Even though canAuth returns privacy enabled, we should still be able to authenticate. + // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) { + // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState()); + // } + // } + + // @Test + // public void testPreAuth_canAuthAndPrivacyEnabledCredentialEnabled() throws Exception { + // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class); + // when(manager + // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt())) + // .thenReturn(true); + // when(mContext.getSystemService(SensorPrivacyManager.class)) + // .thenReturn(manager); + // setupFace(1 /* id */, false /* confirmationAlwaysRequired */, + // mock(IBiometricAuthenticator.class)); + // final PromptInfo promptInfo = + // createPromptInfo(Authenticators.BIOMETRIC_STRONG + // | Authenticators. DEVICE_CREDENTIAL); + // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false); + // assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult()); + // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) { + // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState()); + // } + // } + private void testInvokesCancel(Consumer<AuthSession> sessionConsumer) throws RemoteException { final IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class); @@ -331,7 +390,8 @@ public class AuthSessionTest { userId, promptInfo, TEST_PACKAGE, - checkDevicePolicyManager); + checkDevicePolicyManager, + mContext); } private AuthSession createAuthSession(List<BiometricSensor> sensors, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index e3e3900c47e0..d192697827f6 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -143,8 +143,8 @@ public class BiometricSchedulerTest { final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class); - final BiometricPromptClientMonitor client1 = - new BiometricPromptClientMonitor(mContext, mToken, lazyDaemon1, listener1); + final TestAuthenticationClient client1 = + new TestAuthenticationClient(mContext, lazyDaemon1, mToken, listener1); final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2); final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); @@ -180,8 +180,8 @@ public class BiometricSchedulerTest { @Test public void testCancelNotInvoked_whenOperationWaitingForCookie() { final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> mock(Object.class); - final BiometricPromptClientMonitor client1 = new BiometricPromptClientMonitor(mContext, - mToken, lazyDaemon1, mock(ClientMonitorCallbackConverter.class)); + final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext, + lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class)); final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); // Schedule a BiometricPrompt authentication request @@ -195,6 +195,8 @@ public class BiometricSchedulerTest { // should go back to idle, since in this case the framework has not even requested the HAL // to authenticate yet. mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */); + assertTrue(client1.isAlreadyDone()); + assertTrue(client1.mDestroyed); assertNull(mScheduler.mCurrentOperation); } @@ -316,6 +318,10 @@ public class BiometricSchedulerTest { eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED), eq(0) /* vendorCode */); assertNull(mScheduler.getCurrentClient()); + assertTrue(client1.isAlreadyDone()); + assertTrue(client1.mDestroyed); + assertTrue(client2.isAlreadyDone()); + assertTrue(client2.mDestroyed); } @Test @@ -465,39 +471,9 @@ public class BiometricSchedulerTest { return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer)); } - private static class BiometricPromptClientMonitor extends AuthenticationClient<Object> { - - public BiometricPromptClientMonitor(@NonNull Context context, @NonNull IBinder token, - @NonNull LazyDaemon<Object> lazyDaemon, ClientMonitorCallbackConverter listener) { - super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */, - false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */, - TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */, - 0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class), - false /* isKeyguard */, true /* shouldVibrate */, - false /* isKeyguardBypassEnabled */); - } - - @Override - protected void stopHalOperation() { - } - - @Override - protected void startHalOperation() { - } - - @Override - protected void handleLifecycleAfterAuth(boolean authenticated) { - - } - - @Override - public boolean wasUserDetected() { - return false; - } - } - private static class TestAuthenticationClient extends AuthenticationClient<Object> { int mNumCancels = 0; + boolean mDestroyed = false; public TestAuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token, @@ -530,6 +506,13 @@ public class BiometricSchedulerTest { return false; } + @Override + public void destroy() { + mDestroyed = true; + super.destroy(); + } + + @Override public void cancel() { mNumCancels++; super.cancel(); @@ -595,6 +578,7 @@ public class BiometricSchedulerTest { @Override public void destroy() { + super.destroy(); mDestroyed = true; } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java new file mode 100644 index 000000000000..dc39b6d573db --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2021 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.biometrics.sensors; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.fingerprint.ISidefpsController; +import android.hardware.fingerprint.IUdfpsOverlayController; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; + +@Presubmit +@SmallTest +public class SensorOverlaysTest { + + private static final int SENSOR_ID = 11; + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock private IUdfpsOverlayController mUdfpsOverlayController; + @Mock private ISidefpsController mSidefpsController; + @Mock private AcquisitionClient<?> mAcquisitionClient; + + @Test + public void noopWhenBothNull() { + final SensorOverlays useless = new SensorOverlays(null, null); + useless.show(SENSOR_ID, 2, null); + useless.hide(SENSOR_ID); + } + + @Test + public void testProvidesUdfps() { + final List<IUdfpsOverlayController> udfps = new ArrayList<>(); + SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController); + + sensorOverlays.ifUdfps(udfps::add); + assertThat(udfps).isEmpty(); + + sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController); + sensorOverlays.ifUdfps(udfps::add); + assertThat(udfps).containsExactly(mUdfpsOverlayController); + } + + @Test + public void testShow() throws Exception { + testShow(mUdfpsOverlayController, mSidefpsController); + } + + @Test + public void testShowUdfps() throws Exception { + testShow(mUdfpsOverlayController, null); + } + + @Test + public void testShowSidefps() throws Exception { + testShow(null, mSidefpsController); + } + + private void testShow(IUdfpsOverlayController udfps, ISidefpsController sidefps) + throws Exception { + final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps); + final int reason = BiometricOverlayConstants.REASON_UNKNOWN; + sensorOverlays.show(SENSOR_ID, reason, mAcquisitionClient); + + if (udfps != null) { + verify(mUdfpsOverlayController).showUdfpsOverlay(eq(SENSOR_ID), eq(reason), any()); + } + if (sidefps != null) { + verify(mSidefpsController).show(eq(SENSOR_ID), eq(reason)); + } + } + + @Test + public void testHide() throws Exception { + testHide(mUdfpsOverlayController, mSidefpsController); + } + + @Test + public void testHideUdfps() throws Exception { + testHide(mUdfpsOverlayController, null); + } + + @Test + public void testHideSidefps() throws Exception { + testHide(null, mSidefpsController); + } + + private void testHide(IUdfpsOverlayController udfps, ISidefpsController sidefps) + throws Exception { + final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps); + sensorOverlays.hide(SENSOR_ID); + + if (udfps != null) { + verify(mUdfpsOverlayController).hideUdfpsOverlay(eq(SENSOR_ID)); + } + if (sidefps != null) { + verify(mSidefpsController).hide(eq(SENSOR_ID)); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java index 0cd6d86a3ec9..0ac00aafbf6c 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java @@ -19,13 +19,17 @@ package com.android.server.biometrics.sensors.face.aidl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.biometrics.common.CommonProps; import android.hardware.biometrics.face.IFace; +import android.hardware.biometrics.face.ISession; import android.hardware.biometrics.face.SensorProps; +import android.os.RemoteException; import android.os.UserManager; import android.platform.test.annotations.Presubmit; @@ -33,7 +37,6 @@ import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; -import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.HalClientMonitor; import com.android.server.biometrics.sensors.LockoutResetDispatcher; @@ -55,6 +58,8 @@ public class FaceProviderTest { private Context mContext; @Mock private UserManager mUserManager; + @Mock + private IFace mDaemon; private SensorProps[] mSensorProps; private LockoutResetDispatcher mLockoutResetDispatcher; @@ -65,11 +70,12 @@ public class FaceProviderTest { } @Before - public void setUp() { + public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>()); + when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class)); final SensorProps sensor1 = new SensorProps(); sensor1.commonProps = new CommonProps(); @@ -78,11 +84,11 @@ public class FaceProviderTest { sensor2.commonProps = new CommonProps(); sensor2.commonProps.sensorId = 1; - mSensorProps = new SensorProps[] {sensor1, sensor2}; + mSensorProps = new SensorProps[]{sensor1, sensor2}; mLockoutResetDispatcher = new LockoutResetDispatcher(mContext); - mFaceProvider = new TestableFaceProvider(mContext, mSensorProps, TAG, + mFaceProvider = new TestableFaceProvider(mDaemon, mContext, mSensorProps, TAG, mLockoutResetDispatcher); } @@ -127,17 +133,20 @@ public class FaceProviderTest { } private static class TestableFaceProvider extends FaceProvider { - public TestableFaceProvider(@NonNull Context context, + private final IFace mDaemon; + + TestableFaceProvider(@NonNull IFace daemon, + @NonNull Context context, @NonNull SensorProps[] props, @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher) { super(context, props, halInstanceName, lockoutResetDispatcher); + mDaemon = daemon; } @Override synchronized IFace getHalInstance() { - return mock(IFace.class); + return mDaemon; } } - } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java index b51918e24b13..73f1516562bc 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java @@ -19,14 +19,20 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; import android.hardware.biometrics.common.CommonProps; import android.hardware.biometrics.fingerprint.IFingerprint; +import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.SensorLocation; import android.hardware.biometrics.fingerprint.SensorProps; +import android.os.RemoteException; import android.os.UserManager; import android.platform.test.annotations.Presubmit; @@ -56,8 +62,12 @@ public class FingerprintProviderTest { @Mock private Context mContext; @Mock + private Resources mResources; + @Mock private UserManager mUserManager; @Mock + private IFingerprint mDaemon; + @Mock private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; @Mock private FingerprintStateCallback mFingerprintStateCallback; @@ -71,27 +81,31 @@ public class FingerprintProviderTest { } @Before - public void setUp() { + public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); + when(mContext.getResources()).thenReturn(mResources); + when(mResources.obtainTypedArray(anyInt())).thenReturn(mock(TypedArray.class)); when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>()); + when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class)); final SensorProps sensor1 = new SensorProps(); sensor1.commonProps = new CommonProps(); sensor1.commonProps.sensorId = 0; - sensor1.sensorLocations = new SensorLocation[] {new SensorLocation()}; + sensor1.sensorLocations = new SensorLocation[]{new SensorLocation()}; final SensorProps sensor2 = new SensorProps(); sensor2.commonProps = new CommonProps(); sensor2.commonProps.sensorId = 1; - sensor2.sensorLocations = new SensorLocation[] {new SensorLocation()}; + sensor2.sensorLocations = new SensorLocation[]{new SensorLocation()}; - mSensorProps = new SensorProps[] {sensor1, sensor2}; + mSensorProps = new SensorProps[]{sensor1, sensor2}; mLockoutResetDispatcher = new LockoutResetDispatcher(mContext); - mFingerprintProvider = new TestableFingerprintProvider(mContext, mFingerprintStateCallback, - mSensorProps, TAG, mLockoutResetDispatcher, mGestureAvailabilityDispatcher); + mFingerprintProvider = new TestableFingerprintProvider(mDaemon, mContext, + mFingerprintStateCallback, mSensorProps, TAG, mLockoutResetDispatcher, + mGestureAvailabilityDispatcher); } @SuppressWarnings("rawtypes") @@ -135,7 +149,10 @@ public class FingerprintProviderTest { } private static class TestableFingerprintProvider extends FingerprintProvider { - public TestableFingerprintProvider(@NonNull Context context, + private final IFingerprint mDaemon; + + TestableFingerprintProvider(@NonNull IFingerprint daemon, + @NonNull Context context, @NonNull FingerprintStateCallback fingerprintStateCallback, @NonNull SensorProps[] props, @NonNull String halInstanceName, @@ -143,11 +160,12 @@ public class FingerprintProviderTest { @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { super(context, fingerprintStateCallback, props, halInstanceName, lockoutResetDispatcher, gestureAvailabilityDispatcher); + mDaemon = daemon; } @Override synchronized IFingerprint getHalInstance() { - return mock(IFingerprint.class); + return mDaemon; } } } diff --git a/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java new file mode 100644 index 000000000000..ea746d1f4fd3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2021 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.camera; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.InstrumentationRegistry; + +import android.content.Context; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraMetadata; +import android.view.Display; +import android.view.Surface; + +import java.util.HashMap; + +@RunWith(JUnit4.class) +public class CameraServiceProxyTest { + + @Test + public void testGetCropRotateScale() { + + Context ctx = InstrumentationRegistry.getContext(); + + // Check resizeability and SDK + CameraServiceProxy.TaskInfo taskInfo = new CameraServiceProxy.TaskInfo(); + taskInfo.isResizeable = true; + taskInfo.displayId = Display.DEFAULT_DISPLAY; + taskInfo.isFixedOrientationLandscape = false; + taskInfo.isFixedOrientationPortrait = true; + // Resizeable apps should be ignored + assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo, + Surface.ROTATION_90 , CameraCharacteristics.LENS_FACING_BACK, + /*ignoreResizableAndSdkCheck*/false)).isEqualTo( + CameraMetadata.SCALER_ROTATE_AND_CROP_NONE); + // Resizeable apps will be considered in case the ignore flag is set + assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo, + Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK, + /*ignoreResizableAndSdkCheck*/true)).isEqualTo( + CameraMetadata.SCALER_ROTATE_AND_CROP_90); + taskInfo.isResizeable = false; + // Non-resizeable apps should be considered + assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo, + Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK, + /*ignoreResizableAndSdkCheck*/false)).isEqualTo( + CameraMetadata.SCALER_ROTATE_AND_CROP_90); + // The ignore flag for non-resizeable should have no effect + assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo, + Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK, + /*ignoreResizableAndSdkCheck*/true)).isEqualTo( + CameraMetadata.SCALER_ROTATE_AND_CROP_90); + // Non-fixed orientation should be ignored + taskInfo.isFixedOrientationLandscape = false; + taskInfo.isFixedOrientationPortrait = false; + assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo, + Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK, + /*ignoreResizableAndSdkCheck*/true)).isEqualTo( + CameraMetadata.SCALER_ROTATE_AND_CROP_NONE); + // Check rotation and lens facing combinations + HashMap<Integer, Integer> backFacingMap = new HashMap<Integer, Integer>() {{ + put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE); + put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_90); + put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_270); + put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180); + }}; + taskInfo.isFixedOrientationPortrait = true; + backFacingMap.forEach((key, value) -> { + assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo, + key, CameraCharacteristics.LENS_FACING_BACK, + /*ignoreResizableAndSdkCheck*/true)).isEqualTo(value); + }); + HashMap<Integer, Integer> frontFacingMap = new HashMap<Integer, Integer>() {{ + put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE); + put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_270); + put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_90); + put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180); + }}; + frontFacingMap.forEach((key, value) -> { + assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo, + key, CameraCharacteristics.LENS_FACING_FRONT, + /*ignoreResizableAndSdkCheck*/true)).isEqualTo(value); + }); + } +} diff --git a/services/tests/servicestests/src/com/android/server/camera/OWNERS b/services/tests/servicestests/src/com/android/server/camera/OWNERS new file mode 100644 index 000000000000..f48a95c5b3a3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/camera/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/av:/camera/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index 0248b9b88788..d926dcba54a3 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -265,9 +265,10 @@ public class CompatConfigTest { } @Test - public void testInstallerCanSetOverrides() throws Exception { + public void testInstallerCanAddOverrides() throws Exception { final long disabledChangeId1 = 1234L; final long disabledChangeId2 = 1235L; + final long unknownChangeId = 1236L; // We make disabledChangeId2 non-overridable to make sure it is ignored. CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) .addDisabledOverridableChangeWithId(disabledChangeId1) @@ -284,19 +285,25 @@ public class CompatConfigTest { // Force the validator to prevent overriding non-overridable changes by using a user build. when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); when(mBuildClassifier.isFinalBuild()).thenReturn(true); + Map<Long, PackageOverride> overrides = new HashMap<>(); + overrides.put(disabledChangeId1, new PackageOverride.Builder() + .setMaxVersionCode(99L) + .setEnabled(true) + .build()); + // Adding an unknown change ID to make sure it's skipped if skipUnknownChangeIds is true. + overrides.put(unknownChangeId, new PackageOverride.Builder().setEnabled(false).build()); + CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides); - CompatibilityOverrideConfig config = new CompatibilityOverrideConfig( - Collections.singletonMap(disabledChangeId1, - new PackageOverride.Builder() - .setMaxVersionCode(99L) - .setEnabled(true) - .build())); - - compatConfig.addOverrides(config, "com.some.package"); + compatConfig.addPackageOverrides(config, "com.some.package", /* skipUnknownChangeIds */ + true); assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo)).isTrue(); assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo)).isFalse(); + // Making sure the unknown change ID is still unknown and isChangeEnabled returns true. + assertThat(compatConfig.isKnownChangeId(unknownChangeId)).isFalse(); + assertThat(compatConfig.isChangeEnabled(unknownChangeId, applicationInfo)).isTrue(); } + @Test public void testPreventInstallerSetNonOverridable() throws Exception { final long disabledChangeId1 = 1234L; @@ -326,7 +333,8 @@ public class CompatConfigTest { CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides); assertThrows(SecurityException.class, - () -> compatConfig.addOverrides(config, "com.some.package") + () -> compatConfig.addPackageOverrides(config, "com.some.package", + /* skipUnknownChangeIds */ true) ); assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo)).isTrue(); assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo)).isFalse(); @@ -334,6 +342,38 @@ public class CompatConfigTest { } @Test + public void testCanAddOverridesForUnknownChangeIdOnDebugBuild() throws Exception { + final long disabledChangeId = 1234L; + final long unknownChangeId = 1235L; + // We make disabledChangeId2 non-overridable to make sure it is ignored. + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(disabledChangeId) + .build(); + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.some.package") + .build(); + PackageManager packageManager = mock(PackageManager.class); + when(mContext.getPackageManager()).thenReturn(packageManager); + when(packageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(applicationInfo); + + when(mBuildClassifier.isDebuggableBuild()).thenReturn(true); + Map<Long, PackageOverride> overrides = new HashMap<>(); + overrides.put(disabledChangeId, new PackageOverride.Builder().setEnabled(true).build()); + // Adding an unknown change ID to make sure it isn't skipped if skipUnknownChangeIds is + // false. + overrides.put(unknownChangeId, new PackageOverride.Builder().setEnabled(false).build()); + CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides); + + compatConfig.addPackageOverrides(config, "com.some.package", /* skipUnknownChangeIds */ + false); + assertThat(compatConfig.isChangeEnabled(disabledChangeId, applicationInfo)).isTrue(); + // Making sure the unknown change ID is now known and has an override. + assertThat(compatConfig.isKnownChangeId(unknownChangeId)).isTrue(); + assertThat(compatConfig.isChangeEnabled(unknownChangeId, applicationInfo)).isFalse(); + } + + @Test public void testApplyDeferredOverridesAfterInstallingApp() throws Exception { ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() .withPackageName("com.notinstalled.foo") @@ -377,7 +417,8 @@ public class CompatConfigTest { .setMaxVersionCode(99L) .setEnabled(true) .build())); - compatConfig.addOverrides(config, "com.installed.foo"); + compatConfig.addPackageOverrides(config, "com.installed.foo", /* skipUnknownChangeIds */ + true); assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); // Add override that does include the installed app version @@ -388,7 +429,8 @@ public class CompatConfigTest { .setMaxVersionCode(100L) .setEnabled(true) .build())); - compatConfig.addOverrides(config, "com.installed.foo"); + compatConfig.addPackageOverrides(config, "com.installed.foo", /* skipUnknownChangeIds */ + true); assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue(); } @@ -411,7 +453,8 @@ public class CompatConfigTest { .setMaxVersionCode(99L) .setEnabled(true) .build())); - compatConfig.addOverrides(config, "com.notinstalled.foo"); + compatConfig.addPackageOverrides(config, "com.notinstalled.foo", /* skipUnknownChangeIds */ + true); assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); // Pretend the app is now installed. @@ -557,6 +600,7 @@ public class CompatConfigTest { final long disabledChangeId1 = 1234L; final long disabledChangeId2 = 1235L; final long enabledChangeId = 1236L; + final long unknownChangeId = 1237L; // We make disabledChangeId2 non-overridable to make sure it is ignored. CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) .addDisabledOverridableChangeWithId(disabledChangeId1) @@ -583,6 +627,8 @@ public class CompatConfigTest { Set<Long> overridesToRemove = new HashSet<>(); overridesToRemove.add(disabledChangeId1); overridesToRemove.add(enabledChangeId); + // Adding an unknown change ID to make sure it's skipped. + overridesToRemove.add(unknownChangeId); CompatibilityOverridesToRemoveConfig config = new CompatibilityOverridesToRemoveConfig( overridesToRemove); @@ -590,6 +636,8 @@ public class CompatConfigTest { assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo)).isFalse(); assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo)).isTrue(); assertThat(compatConfig.isChangeEnabled(enabledChangeId, applicationInfo)).isTrue(); + // Making sure the unknown change ID is still unknown. + assertThat(compatConfig.isKnownChangeId(unknownChangeId)).isFalse(); } @Test @@ -797,18 +845,18 @@ public class CompatConfigTest { .build()); when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt())) .thenThrow(new NameNotFoundException()); - compatConfig.addOverrides( + compatConfig.addPackageOverrides( new CompatibilityOverrideConfig( Collections.singletonMap( 1L, new PackageOverride.Builder().setEnabled(true).build())), - "foo.bar"); - compatConfig.addOverrides( + "foo.bar", /* skipUnknownChangeIds */ true); + compatConfig.addPackageOverrides( new CompatibilityOverrideConfig( Collections.singletonMap( 2L, new PackageOverride.Builder().setEnabled(false).build())), - "bar.baz"); + "bar.baz", /* skipUnknownChangeIds */ true); assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<overrides>\n" @@ -847,12 +895,12 @@ public class CompatConfigTest { compatConfig.forceNonDebuggableFinalForTest(true); compatConfig.initOverrides(overridesFile, new File("")); - compatConfig.addOverrides(new CompatibilityOverrideConfig(Collections.singletonMap(1L, - new PackageOverride.Builder() + compatConfig.addPackageOverrides(new CompatibilityOverrideConfig( + Collections.singletonMap(1L, new PackageOverride.Builder() .setMinVersionCode(99L) .setMaxVersionCode(101L) .setEnabled(true) - .build())), "foo.bar"); + .build())), "foo.bar", /* skipUnknownChangeIds */ true); assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<overrides>\n" diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 3ac30d0258a5..97fb399c63ef 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -51,6 +51,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -101,6 +102,7 @@ import android.graphics.Color; import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; import android.net.Uri; +import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Process; @@ -184,6 +186,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public DevicePolicyManager parentDpm; public DevicePolicyManagerServiceTestable dpms; + private boolean mIsAutomotive; + /* * The CA cert below is the content of cacert.pem as generated by: * @@ -266,6 +270,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { setUpUserManager(); when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true); + + mIsAutomotive = mContext.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } private TransferOwnershipMetadataManager getMockTransferMetadataManager() { @@ -2096,9 +2103,12 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL); - setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID); + setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID, null, + Build.VERSION_CODES.Q); dpm.setActiveAdmin(admin1, /* replace =*/ false, UserHandle.USER_SYSTEM); + + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; boolean originalCameraDisabled = dpm.getCameraDisabled(admin1); assertExpectException(SecurityException.class, /* messageRegex= */ null, () -> dpm.setCameraDisabled(admin1, true)); @@ -2117,10 +2127,13 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertThat(dpm.getPasswordExpirationTimeout(admin1)) .isEqualTo(originalPasswordExpirationTimeout); - int originalPasswordQuality = dpm.getPasswordQuality(admin1); - assertExpectException(SecurityException.class, /* messageRegex= */ null, - () -> dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_NUMERIC)); - assertThat(dpm.getPasswordQuality(admin1)).isEqualTo(originalPasswordQuality); + if (isDeprecatedPasswordApisSupported()) { + int originalPasswordQuality = dpm.getPasswordQuality(admin1); + assertExpectException(SecurityException.class, /* messageRegex= */ null, + () -> dpm.setPasswordQuality(admin1, + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC)); + assertThat(dpm.getPasswordQuality(admin1)).isEqualTo(originalPasswordQuality); + } } @Test @@ -2665,8 +2678,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID); // Test 1. Caller doesn't have DO or DA. - assertExpectException(SecurityException.class, /* messageRegex= */ "No active admin", - () -> dpm.getWifiMacAddress(admin1)); + assertExpectException(SecurityException.class, /* messageRegex= */ + "does not exist or is not owned by uid", () -> dpm.getWifiMacAddress(admin1)); // DO needs to be an DA. dpm.setActiveAdmin(admin1, /* replace =*/ false); @@ -5231,6 +5244,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testIsActivePasswordSufficient() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; mContext.packageName = admin1.getPackageName(); setupDeviceOwner(); @@ -5283,6 +5298,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testIsActivePasswordSufficient_noLockScreen() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // If there is no lock screen, the password is considered empty no matter what, because // it provides no security. when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(false); @@ -5363,6 +5380,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testGetAggregatedPasswordMetrics_IgnoreProfileRequirement() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -5392,6 +5411,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testCanSetPasswordRequirementOnParentPreS() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -5407,6 +5428,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testCannotSetPasswordRequirementOnParent() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -5427,6 +5450,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_SeparateWorkChallenge_ProfileQualityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with empty separate challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5450,6 +5475,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_SeparateWorkChallenge_ProfileComplexityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with empty separate challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5473,6 +5500,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_SeparateWorkChallenge_ParentQualityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with empty separate challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5519,6 +5548,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_UnifiedWorkChallenge_ProfileQualityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with unified challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5565,6 +5596,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_UnifiedWorkChallenge_ParentQualityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with unified challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5625,6 +5658,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testPasswordQualityAppliesToParentPreS() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -7285,6 +7320,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_UnauthorizedCallersOnDO() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); // DO must be able to set it. @@ -7300,6 +7337,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_UnauthorizedCallersOnPO() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); // PO must be able to set it. @@ -7314,6 +7353,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_validValuesOnly() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); @@ -7335,6 +7376,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_setAndGet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); @@ -7348,6 +7391,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexityOnParent_setAndGet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -7366,6 +7411,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_isSufficient() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; mContext.packageName = admin1.getPackageName(); setupDeviceOwner(); @@ -7395,6 +7442,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_resetBySettingQuality() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); @@ -7407,6 +7456,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_overridesQuality() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); @@ -7421,6 +7472,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexityFailsWithQualityOnParent() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -7435,6 +7488,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetQualityOnParentFailsWithComplexityOnProfile() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -8015,4 +8070,13 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(mContext.getResources().getStringArray(R.array.vendor_policy_exempt_apps)) .thenReturn(new String[0]); } + + private boolean isDeprecatedPasswordApisSupported() { + return !mIsAutomotive; + } + + private void assumeDeprecatedPasswordApisSupported() { + assumeTrue("device doesn't support deprecated password APIs", + isDeprecatedPasswordApisSupported()); + } } diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index ff8fbda6c83e..ee0723b8b471 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -18,6 +18,8 @@ package com.android.server.devicestate; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; @@ -33,8 +35,12 @@ import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.wm.ActivityTaskManagerInternal; +import com.android.server.wm.WindowProcessController; + import junit.framework.Assert; import org.junit.Before; @@ -55,10 +61,15 @@ import javax.annotation.Nullable; @Presubmit @RunWith(AndroidJUnit4.class) public final class DeviceStateManagerServiceTest { - private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT"); - private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(1, "OTHER"); + private static final DeviceState DEFAULT_DEVICE_STATE = + new DeviceState(0, "DEFAULT", 0 /* flags */); + private static final DeviceState OTHER_DEVICE_STATE = + new DeviceState(1, "OTHER", 0 /* flags */); // A device state that is not reported as being supported for the default test provider. - private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(255, "UNSUPPORTED"); + private static final DeviceState UNSUPPORTED_DEVICE_STATE = + new DeviceState(255, "UNSUPPORTED", 0 /* flags */); + + private static final int FAKE_PROCESS_ID = 100; private TestDeviceStatePolicy mPolicy; private TestDeviceStateProvider mProvider; @@ -69,6 +80,25 @@ public final class DeviceStateManagerServiceTest { mProvider = new TestDeviceStateProvider(); mPolicy = new TestDeviceStatePolicy(mProvider); mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy); + + // Necessary to allow us to check for top app process id in tests + mService.mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class); + WindowProcessController windowProcessController = mock(WindowProcessController.class); + when(mService.mActivityTaskManagerInternal.getTopApp()) + .thenReturn(windowProcessController); + when(windowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID); + + flushHandler(); // Flush the handler to ensure the initial values are committed. + } + + private void flushHandler() { + flushHandler(1); + } + + private void flushHandler(int count) { + for (int i = 0; i < count; i++) { + mService.getHandler().runWithScissors(() -> {}, 0); + } } @Test @@ -80,6 +110,7 @@ public final class DeviceStateManagerServiceTest { DEFAULT_DEVICE_STATE.getIdentifier()); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); + flushHandler(); assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE)); @@ -92,6 +123,7 @@ public final class DeviceStateManagerServiceTest { mPolicy.blockConfigure(); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); + flushHandler(); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE)); assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE)); @@ -99,6 +131,7 @@ public final class DeviceStateManagerServiceTest { OTHER_DEVICE_STATE.getIdentifier()); mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier()); + flushHandler(); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE)); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); @@ -106,6 +139,7 @@ public final class DeviceStateManagerServiceTest { OTHER_DEVICE_STATE.getIdentifier()); mPolicy.resumeConfigure(); + flushHandler(); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); @@ -149,6 +183,7 @@ public final class DeviceStateManagerServiceTest { assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE }); + flushHandler(); // The current committed and requests states do not change because the current state remains // supported. @@ -166,6 +201,7 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().registerCallback(callback); // An initial callback will be triggered on registration, so we clear it here. + flushHandler(); callback.clearLastNotifiedInfo(); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); @@ -174,6 +210,7 @@ public final class DeviceStateManagerServiceTest { mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE }); + flushHandler(); // The current committed and requests states do not change because the current state remains // supported. @@ -203,12 +240,14 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().registerCallback(callback); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); + flushHandler(); assertEquals(callback.getLastNotifiedInfo().baseState, OTHER_DEVICE_STATE.getIdentifier()); assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE.getIdentifier()); mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier()); + flushHandler(); assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE.getIdentifier()); assertEquals(callback.getLastNotifiedInfo().currentState, @@ -216,6 +255,7 @@ public final class DeviceStateManagerServiceTest { mPolicy.blockConfigure(); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); + flushHandler(); // The callback should not have been notified of the state change as the policy is still // pending callback. assertEquals(callback.getLastNotifiedInfo().baseState, @@ -224,6 +264,7 @@ public final class DeviceStateManagerServiceTest { DEFAULT_DEVICE_STATE.getIdentifier()); mPolicy.resumeConfigure(); + flushHandler(); // Now that the policy is finished processing the callback should be notified of the state // change. assertEquals(callback.getLastNotifiedInfo().baseState, @@ -236,6 +277,7 @@ public final class DeviceStateManagerServiceTest { public void registerCallback_emitsInitialValue() throws RemoteException { TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); + flushHandler(); assertNotNull(callback.getLastNotifiedInfo()); assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE.getIdentifier()); @@ -247,6 +289,7 @@ public final class DeviceStateManagerServiceTest { public void requestState() throws RemoteException { TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); + flushHandler(); final IBinder token = new Binder(); assertEquals(callback.getLastNotifiedStatus(token), @@ -254,6 +297,10 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(), 0 /* flags */); + // Flush the handler twice. The first flush ensures the request is added and the policy is + // notified, while the second flush ensures the callback is notified once the change is + // committed. + flushHandler(2 /* count */); assertEquals(callback.getLastNotifiedStatus(token), TestDeviceStateManagerCallback.STATUS_ACTIVE); @@ -271,6 +318,7 @@ public final class DeviceStateManagerServiceTest { OTHER_DEVICE_STATE.getIdentifier()); mService.getBinderService().cancelRequest(token); + flushHandler(); assertEquals(callback.getLastNotifiedStatus(token), TestDeviceStateManagerCallback.STATUS_CANCELED); @@ -287,10 +335,12 @@ public final class DeviceStateManagerServiceTest { DEFAULT_DEVICE_STATE.getIdentifier()); } + @FlakyTest(bugId = 200332057) @Test public void requestState_pendingStateAtRequest() throws RemoteException { TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); + flushHandler(); mPolicy.blockConfigure(); @@ -303,6 +353,10 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestState(firstRequestToken, OTHER_DEVICE_STATE.getIdentifier(), 0 /* flags */); + // Flush the handler twice. The first flush ensures the request is added and the policy is + // notified, while the second flush ensures the callback is notified once the change is + // committed. + flushHandler(2 /* count */); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE)); @@ -312,8 +366,8 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestState(secondRequestToken, DEFAULT_DEVICE_STATE.getIdentifier(), 0 /* flags */); - mPolicy.resumeConfigureOnce(); + flushHandler(); // First request status is now suspended as there is another pending request. assertEquals(callback.getLastNotifiedStatus(firstRequestToken), @@ -330,6 +384,7 @@ public final class DeviceStateManagerServiceTest { DEFAULT_DEVICE_STATE.getIdentifier()); mPolicy.resumeConfigure(); + flushHandler(); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); @@ -339,6 +394,7 @@ public final class DeviceStateManagerServiceTest { // Now cancel the second request to make the first request active. mService.getBinderService().cancelRequest(secondRequestToken); + flushHandler(); assertEquals(callback.getLastNotifiedStatus(firstRequestToken), TestDeviceStateManagerCallback.STATUS_ACTIVE); @@ -356,6 +412,7 @@ public final class DeviceStateManagerServiceTest { public void requestState_sameAsBaseState() throws RemoteException { TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); + flushHandler(); final IBinder token = new Binder(); assertEquals(callback.getLastNotifiedStatus(token), @@ -363,6 +420,7 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestState(token, DEFAULT_DEVICE_STATE.getIdentifier(), 0 /* flags */); + flushHandler(); assertEquals(callback.getLastNotifiedStatus(token), TestDeviceStateManagerCallback.STATUS_ACTIVE); @@ -372,6 +430,7 @@ public final class DeviceStateManagerServiceTest { public void requestState_flagCancelWhenBaseChanges() throws RemoteException { TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); + flushHandler(); final IBinder token = new Binder(); assertEquals(callback.getLastNotifiedStatus(token), @@ -379,6 +438,10 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(), DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES); + // Flush the handler twice. The first flush ensures the request is added and the policy is + // notified, while the second flush ensures the callback is notified once the change is + // committed. + flushHandler(2 /* count */); assertEquals(callback.getLastNotifiedStatus(token), TestDeviceStateManagerCallback.STATUS_ACTIVE); @@ -391,6 +454,7 @@ public final class DeviceStateManagerServiceTest { OTHER_DEVICE_STATE.getIdentifier()); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); + flushHandler(); // Request is canceled because the base state changed. assertEquals(callback.getLastNotifiedStatus(token), @@ -403,10 +467,12 @@ public final class DeviceStateManagerServiceTest { OTHER_DEVICE_STATE.getIdentifier()); } + @FlakyTest(bugId = 200332057) @Test public void requestState_becomesUnsupported() throws RemoteException { TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); + flushHandler(); final IBinder token = new Binder(); assertEquals(callback.getLastNotifiedStatus(token), @@ -414,6 +480,7 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(), 0 /* flags */); + flushHandler(); assertEquals(callback.getLastNotifiedStatus(token), TestDeviceStateManagerCallback.STATUS_ACTIVE); @@ -425,6 +492,7 @@ public final class DeviceStateManagerServiceTest { OTHER_DEVICE_STATE.getIdentifier()); mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE }); + flushHandler(); // Request is canceled because the state is no longer supported. assertEquals(callback.getLastNotifiedStatus(token), diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java index b5c8053ad77e..e286cb27cc41 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java @@ -41,24 +41,26 @@ public final class DeviceStateTest { @Test public void testConstruct() { final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE /* identifier */, - "CLOSED" /* name */); + "CLOSED" /* name */, DeviceState.FLAG_CANCEL_STICKY_REQUESTS /* flags */); assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE); assertEquals(state.getName(), "CLOSED"); + assertEquals(state.getFlags(), DeviceState.FLAG_CANCEL_STICKY_REQUESTS); } @Test public void testConstruct_nullName() { final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE /* identifier */, - null /* name */); + null /* name */, 0/* flags */); assertEquals(state.getIdentifier(), MAXIMUM_DEVICE_STATE); assertNull(state.getName()); + assertEquals(state.getFlags(), 0); } @Test public void testConstruct_tooLargeIdentifier() { assertThrows(IllegalArgumentException.class, () -> { final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE + 1 /* identifier */, - null /* name */); + null /* name */, 0 /* flags */); }); } @@ -66,7 +68,7 @@ public final class DeviceStateTest { public void testConstruct_tooSmallIdentifier() { assertThrows(IllegalArgumentException.class, () -> { final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE - 1 /* identifier */, - null /* name */); + null /* name */, 0 /* flags */); }); } } diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java new file mode 100644 index 000000000000..c9cf2f06640d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2021 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.devicestate; + +import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE; +import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED; +import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNull; + +import android.annotation.Nullable; +import android.hardware.devicestate.DeviceStateRequest; +import android.os.Binder; +import android.platform.test.annotations.Presubmit; + +import androidx.annotation.NonNull; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashMap; +import java.util.Map; + +/** + * Unit tests for {@link OverrideRequestController}. + * <p/> + * Run with <code>atest OverrideRequestControllerTest</code>. + */ +@Presubmit +@RunWith(AndroidJUnit4.class) +public final class OverrideRequestControllerTest { + private TestStatusChangeListener mStatusListener; + private OverrideRequestController mController; + + @Before + public void setup() { + mStatusListener = new TestStatusChangeListener(); + mController = new OverrideRequestController(mStatusListener); + } + + @Test + public void addRequest() { + OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */); + assertNull(mStatusListener.getLastStatus(request)); + + mController.addRequest(request); + assertEquals(mStatusListener.getLastStatus(request).intValue(), STATUS_ACTIVE); + } + + @Test + public void addRequest_suspendExistingRequest() { + OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */); + assertNull(mStatusListener.getLastStatus(firstRequest)); + + mController.addRequest(firstRequest); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + + OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */); + assertNull(mStatusListener.getLastStatus(secondRequest)); + + mController.addRequest(secondRequest); + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); + } + + @Test + public void addRequest_cancelActiveRequest() { + OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */); + OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */); + + mController.addRequest(firstRequest); + mController.addRequest(secondRequest); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); + + mController.cancelRequest(secondRequest.getToken()); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + } + + @Test + public void addRequest_cancelSuspendedRequest() { + OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */); + OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */); + + mController.addRequest(firstRequest); + mController.addRequest(secondRequest); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); + + mController.cancelRequest(firstRequest.getToken()); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); + } + + @Test + public void handleBaseStateChanged() { + OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */); + OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, + DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */); + + mController.addRequest(firstRequest); + mController.addRequest(secondRequest); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); + + mController.handleBaseStateChanged(); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + } + + @Test + public void handleProcessDied() { + OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */); + OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */, + 0 /* requestedState */, 0 /* flags */); + + mController.addRequest(firstRequest); + mController.addRequest(secondRequest); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); + + mController.handleProcessDied(1); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + + mController.handleProcessDied(0); + + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); + } + + @Test + public void handleProcessDied_stickyRequests() { + mController.setStickyRequestsAllowed(true); + + OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */); + OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */, + 0 /* requestedState */, 0 /* flags */); + + mController.addRequest(firstRequest); + mController.addRequest(secondRequest); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); + + mController.handleProcessDied(1); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); + + mController.cancelStickyRequests(); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + } + + @Test + public void handleNewSupportedStates() { + OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 1 /* requestedState */, 0 /* flags */); + OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 2 /* requestedState */, 0 /* flags */); + + mController.addRequest(firstRequest); + mController.addRequest(secondRequest); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); + + mController.handleNewSupportedStates(new int[]{ 0, 1 }); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + + mController.handleNewSupportedStates(new int[]{ 0 }); + + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); + } + + private static final class TestStatusChangeListener implements + OverrideRequestController.StatusChangeListener { + private Map<OverrideRequest, Integer> mLastStatusMap = new HashMap<>(); + + @Override + public void onStatusChanged(@NonNull OverrideRequest request, int newStatus) { + mLastStatusMap.put(request, newStatus); + } + + @Nullable + public Integer getLastStatus(OverrideRequest request) { + return mLastStatusMap.get(request); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index 5ba375b922e2..7c55716c5e99 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -885,6 +885,61 @@ public class DisplayManagerServiceTest { assertFalse(callback.mDisplayAddedCalled); } + + + @Test + public void testSettingTwoBrightnessConfigurationsOnMultiDisplay() { + Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); + + // get the first two internal displays + Display[] displays = displayManager.getDisplays(); + Display internalDisplayOne = null; + Display internalDisplayTwo = null; + for (Display display : displays) { + if (display.getType() == Display.TYPE_INTERNAL) { + if (internalDisplayOne == null) { + internalDisplayOne = display; + } else { + internalDisplayTwo = display; + break; + } + } + } + + // return if there are fewer than 2 displays on this device + if (internalDisplayOne == null || internalDisplayTwo == null) { + return; + } + + final String uniqueDisplayIdOne = internalDisplayOne.getUniqueId(); + final String uniqueDisplayIdTwo = internalDisplayTwo.getUniqueId(); + + BrightnessConfiguration configOne = + new BrightnessConfiguration.Builder( + new float[]{0.0f, 12345.0f}, new float[]{15.0f, 400.0f}) + .setDescription("model:1").build(); + BrightnessConfiguration configTwo = + new BrightnessConfiguration.Builder( + new float[]{0.0f, 6789.0f}, new float[]{12.0f, 300.0f}) + .setDescription("model:2").build(); + + displayManager.setBrightnessConfigurationForDisplay(configOne, + uniqueDisplayIdOne); + displayManager.setBrightnessConfigurationForDisplay(configTwo, + uniqueDisplayIdTwo); + + BrightnessConfiguration configFromOne = + displayManager.getBrightnessConfigurationForDisplay(uniqueDisplayIdOne); + BrightnessConfiguration configFromTwo = + displayManager.getBrightnessConfigurationForDisplay(uniqueDisplayIdTwo); + + assertNotNull(configFromOne); + assertEquals(configOne, configFromOne); + assertEquals(configTwo, configFromTwo); + + } + private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled) throws Exception { DisplayManagerService displayManager = diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java index 196454bd32ce..57a9cb278c80 100644 --- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java @@ -17,13 +17,16 @@ package com.android.server.display; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import android.content.Context; import android.hardware.display.BrightnessConfiguration; import android.util.Pair; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -144,15 +147,93 @@ public class PersistentDataStoreTest { } @Test + public void testStoreAndReloadOfDisplayBrightnessConfigurations() { + final String uniqueDisplayId = "test:123"; + int userSerial = 0; + String packageName = "pdsTestPackage"; + final float[] lux = { 0f, 10f }; + final float[] nits = {1f, 100f }; + final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits) + .setDescription("a description") + .build(); + mDataStore.loadIfNeeded(); + assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId, + userSerial)); + + DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) { + @Override + public boolean hasStableUniqueId() { + return true; + } + + @Override + public DisplayDeviceInfo getDisplayDeviceInfoLocked() { + return null; + } + }; + + mDataStore.setBrightnessConfigurationForDisplayLocked(config, testDisplayDevice, userSerial, + packageName); + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + mInjector.setWriteStream(baos); + mDataStore.saveIfNeeded(); + assertTrue(mInjector.wasWriteSuccessful()); + TestInjector newInjector = new TestInjector(); + PersistentDataStore newDataStore = new PersistentDataStore(newInjector); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + newInjector.setReadStream(bais); + newDataStore.loadIfNeeded(); + assertNotNull(newDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId, + userSerial)); + assertEquals(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId, + userSerial), newDataStore.getBrightnessConfigurationForDisplayLocked( + uniqueDisplayId, userSerial)); + } + + @Test + public void testSetBrightnessConfigurationFailsWithUnstableId() { + final String uniqueDisplayId = "test:123"; + int userSerial = 0; + String packageName = "pdsTestPackage"; + final float[] lux = { 0f, 10f }; + final float[] nits = {1f, 100f }; + final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits) + .setDescription("a description") + .build(); + mDataStore.loadIfNeeded(); + assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId, + userSerial)); + + DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) { + @Override + public boolean hasStableUniqueId() { + return false; + } + + @Override + public DisplayDeviceInfo getDisplayDeviceInfoLocked() { + return null; + } + }; + + assertFalse(mDataStore.setBrightnessConfigurationForDisplayLocked( + config, testDisplayDevice, userSerial, packageName)); + } + + @Test public void testStoreAndReloadOfBrightnessConfigurations() { final float[] lux = { 0f, 10f }; final float[] nits = {1f, 100f }; final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits) .setDescription("a description") .build(); + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + String packageName = context.getPackageName(); + mDataStore.loadIfNeeded(); assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/)); - mDataStore.setBrightnessConfigurationForUser(config, 0, "packagename"); + mDataStore.setBrightnessConfigurationForUser(config, 0, packageName); final ByteArrayOutputStream baos = new ByteArrayOutputStream(); mInjector.setWriteStream(baos); @@ -173,17 +254,18 @@ public class PersistentDataStoreTest { public void testNullBrightnessConfiguration() { final float[] lux = { 0f, 10f }; final float[] nits = {1f, 100f }; + int userSerial = 0; final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits) .setDescription("a description") .build(); mDataStore.loadIfNeeded(); - assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/)); + assertNull(mDataStore.getBrightnessConfiguration(userSerial)); - mDataStore.setBrightnessConfigurationForUser(config, 0, "packagename"); - assertNotNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/)); + mDataStore.setBrightnessConfigurationForUser(config, userSerial, "packagename"); + assertNotNull(mDataStore.getBrightnessConfiguration(userSerial)); - mDataStore.setBrightnessConfigurationForUser(null, 0, "packagename"); - assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/)); + mDataStore.setBrightnessConfigurationForUser(null, userSerial, "packagename"); + assertNull(mDataStore.getBrightnessConfiguration(userSerial)); } public class TestInjector extends PersistentDataStore.Injector { diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java index 976a588273a7..18992ecfd3a4 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import static com.android.compatibility.common.util.ShellUtils.runShellCommand; + import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.fail; @@ -27,9 +29,12 @@ import static java.lang.reflect.Modifier.isPublic; import static java.lang.reflect.Modifier.isStatic; import android.annotation.Nullable; +import android.app.AppGlobals; import android.content.IIntentReceiver; +import android.content.pm.IPackageManager; import android.content.pm.PackageManagerInternal; import android.os.Bundle; +import android.os.UserHandle; import android.util.SparseArray; import androidx.test.runner.AndroidJUnit4; @@ -62,8 +67,18 @@ import java.util.regex.Pattern; // bit FrameworksServicesTests:com.android.server.pm.PackageManagerServiceTest @RunWith(AndroidJUnit4.class) public class PackageManagerServiceTest { + + private static final String PACKAGE_NAME = "com.android.frameworks.servicestests"; + + private static final String TEST_DATA_PATH = "/data/local/tmp/servicestests/"; + private static final String TEST_APP_APK = "StubTestApp.apk"; + private static final String TEST_PKG_NAME = "com.android.servicestests.apps.stubapp"; + + private IPackageManager mIPackageManager; + @Before public void setUp() throws Exception { + mIPackageManager = AppGlobals.getPackageManager(); } @After @@ -599,4 +614,26 @@ public class PackageManagerServiceTest { Collections.sort(knownPackageIds); return knownPackageIds; } + + @Test + public void testSetSplashScreenTheme_samePackage_succeeds() throws Exception { + mIPackageManager.setSplashScreenTheme(PACKAGE_NAME, null /* themeName */, + UserHandle.myUserId()); + // Invoking setSplashScreenTheme on the same package shouldn't get any exception. + } + + @Test + public void testSetSplashScreenTheme_differentPackage_fails() throws Exception { + final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK); + try { + runShellCommand("pm install " + testApk); + mIPackageManager.setSplashScreenTheme(TEST_PKG_NAME, null /* themeName */, + UserHandle.myUserId()); + fail("setSplashScreenTheme did not throw SecurityException as expected"); + } catch (SecurityException e) { + // expected + } finally { + runShellCommand("pm uninstall " + TEST_PKG_NAME); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt index 581ff5472e92..9099bb515361 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt @@ -341,10 +341,8 @@ open class AndroidPackageParsingTestBase { launchToken=${this.launchToken} lockTaskLaunchMode=${this.lockTaskLaunchMode} logo=${this.logo} - maxAspectRatio=${this.maxAspectRatio} maxRecents=${this.maxRecents} metaData=${this.metaData.dumpToString()} - minAspectRatio=${this.minAspectRatio} name=${this.name} nonLocalizedLabel=${ // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java index 4d2d2f1a4b7d..761cea79df28 100644 --- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java @@ -150,8 +150,9 @@ public final class DeviceStateProviderImplTest { provider.setListener(listener); verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); - final DeviceState[] expectedStates = new DeviceState[]{ new DeviceState(1, ""), - new DeviceState(2, "") }; + final DeviceState[] expectedStates = new DeviceState[]{ + new DeviceState(1, "", 0 /* flags */), + new DeviceState(2, "", 0 /* flags */) }; assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue()); verify(listener).onStateChanged(mIntegerCaptor.capture()); @@ -159,6 +160,64 @@ public final class DeviceStateProviderImplTest { } @Test + public void create_stateWithCancelStickyRequestFlag() { + String configString = "<device-state-config>\n" + + " <device-state>\n" + + " <identifier>1</identifier>\n" + + " <flags>\n" + + " <flag>FLAG_CANCEL_STICKY_REQUESTS</flag>\n" + + " </flags>\n" + + " <conditions/>\n" + + " </device-state>\n" + + " <device-state>\n" + + " <identifier>2</identifier>\n" + + " <conditions/>\n" + + " </device-state>\n" + + "</device-state-config>\n"; + DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString); + DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext, + config); + + DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class); + provider.setListener(listener); + + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); + final DeviceState[] expectedStates = new DeviceState[]{ + new DeviceState(1, "", DeviceState.FLAG_CANCEL_STICKY_REQUESTS), + new DeviceState(2, "", 0 /* flags */) }; + assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue()); + } + + @Test + public void create_stateWithInvalidFlag() { + String configString = "<device-state-config>\n" + + " <device-state>\n" + + " <identifier>1</identifier>\n" + + " <flags>\n" + + " <flag>INVALID_FLAG</flag>\n" + + " </flags>\n" + + " <conditions/>\n" + + " </device-state>\n" + + " <device-state>\n" + + " <identifier>2</identifier>\n" + + " <conditions/>\n" + + " </device-state>\n" + + "</device-state-config>\n"; + DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString); + DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext, + config); + + DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class); + provider.setListener(listener); + + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); + final DeviceState[] expectedStates = new DeviceState[]{ + new DeviceState(1, "", 0 /* flags */), + new DeviceState(2, "", 0 /* flags */) }; + assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue()); + } + + @Test public void create_lidSwitch() { String configString = "<device-state-config>\n" + " <device-state>\n" @@ -187,8 +246,9 @@ public final class DeviceStateProviderImplTest { provider.setListener(listener); verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); - final DeviceState[] expectedStates = new DeviceState[]{ new DeviceState(1, ""), - new DeviceState(2, "CLOSED") }; + final DeviceState[] expectedStates = new DeviceState[]{ + new DeviceState(1, "", 0 /* flags */), + new DeviceState(2, "CLOSED", 0 /* flags */) }; assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue()); // onStateChanged() should not be called because the provider has not yet been notified of @@ -264,8 +324,11 @@ public final class DeviceStateProviderImplTest { verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); assertArrayEquals( - new DeviceState[]{ new DeviceState(1, "CLOSED"), new DeviceState(2, "HALF_OPENED"), - new DeviceState(3, "OPENED") }, mDeviceStateArrayCaptor.getValue()); + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */) }, + mDeviceStateArrayCaptor.getValue()); // onStateChanged() should not be called because the provider has not yet been notified of // the initial sensor state. verify(listener, never()).onStateChanged(mIntegerCaptor.capture()); @@ -350,8 +413,10 @@ public final class DeviceStateProviderImplTest { verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); assertArrayEquals( - new DeviceState[]{ new DeviceState(1, "CLOSED"), new DeviceState(2, "HALF_OPENED"), - }, mDeviceStateArrayCaptor.getValue()); + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */) + }, mDeviceStateArrayCaptor.getValue()); // onStateChanged() should be called because the provider could not find the sensor. verify(listener).onStateChanged(mIntegerCaptor.capture()); assertEquals(1, mIntegerCaptor.getValue().intValue()); diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java index 26b34fdd4e04..304fe5a1c9c3 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -30,6 +30,7 @@ import android.hardware.power.stats.PowerEntity; import android.hardware.power.stats.State; import android.hardware.power.stats.StateResidency; import android.hardware.power.stats.StateResidencyResult; +import android.os.Looper; import androidx.test.InstrumentationRegistry; @@ -145,12 +146,12 @@ public class PowerStatsServiceTest { } @Override - PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath, - String meterFilename, String meterCacheFilename, + PowerStatsLogger createPowerStatsLogger(Context context, Looper looper, + File dataStoragePath, String meterFilename, String meterCacheFilename, String modelFilename, String modelCacheFilename, String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { - mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, + mPowerStatsLogger = new PowerStatsLogger(context, looper, dataStoragePath, meterFilename, meterCacheFilename, modelFilename, modelCacheFilename, residencyFilename, residencyCacheFilename, diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/UptimeTimerTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/UptimeTimerTest.java new file mode 100644 index 000000000000..38297bf624b7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/UptimeTimerTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021 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.soundtrigger_middleware; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.atomic.AtomicBoolean; + +@RunWith(AndroidJUnit4.class) +public class UptimeTimerTest { + private static final String TAG = "UptimeTimerTest"; + + @Test + public void testBasic() throws InterruptedException { + AtomicBoolean taskRan = new AtomicBoolean(false); + UptimeTimer timer = new UptimeTimer("TestTimer"); + timer.createTask(() -> taskRan.set(true), 100); + Thread.sleep(50); + boolean before = taskRan.get(); + Thread.sleep(100); + boolean after = taskRan.get(); + assertFalse(before); + assertTrue(after); + } + + @Test + public void testCancel() throws InterruptedException { + AtomicBoolean taskRan = new AtomicBoolean(false); + UptimeTimer timer = new UptimeTimer("TestTimer"); + UptimeTimer.Task task = timer.createTask(() -> taskRan.set(true), 100); + Thread.sleep(50); + boolean before = taskRan.get(); + task.cancel(); + Thread.sleep(100); + boolean after = taskRan.get(); + assertFalse(before); + assertFalse(after); + } +} diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 5a00e0d6530d..62e0a19abd7f 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -998,6 +998,8 @@ public class VibratorManagerServiceTest { throws Exception { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); + setRingerMode(AudioManager.RINGER_MODE_NORMAL); + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); createSystemReadyService(); IExternalVibrationController firstController = mock(IExternalVibrationController.class); @@ -1006,8 +1008,11 @@ public class VibratorManagerServiceTest { firstController); int firstScale = mExternalVibratorService.onExternalVibrationStart(firstVibration); - ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS, - secondController); + AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) + .build(); + ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME, + ringtoneAudioAttrs, secondController); int secondScale = mExternalVibratorService.onExternalVibrationStart(secondVibration); assertEquals(IExternalVibratorService.SCALE_NONE, firstScale); @@ -1040,6 +1045,37 @@ public class VibratorManagerServiceTest { assertEquals(Arrays.asList(true), mVibratorProviders.get(1).getExternalControlStates()); } + @Test + public void onExternalVibration_withRingtone_usesRingerModeSettings() { + mockVibrators(1); + mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); + mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM); + AudioAttributes audioAttrs = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) + .build(); + ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs, + mock(IExternalVibrationController.class)); + + setRingerMode(AudioManager.RINGER_MODE_NORMAL); + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); + setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + createSystemReadyService(); + int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); + assertEquals(IExternalVibratorService.SCALE_MUTE, scale); + + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); + setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1); + createSystemReadyService(); + scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); + assertEquals(IExternalVibratorService.SCALE_NONE, scale); + + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); + setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + createSystemReadyService(); + scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); + assertEquals(IExternalVibratorService.SCALE_NONE, scale); + } + private VibrationEffectSegment expectedPrebaked(int effectId) { return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); } diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml index 78afb7b72c04..3cc105ebb746 100644 --- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml +++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml @@ -18,6 +18,7 @@ package="com.android.servicestests.apps.simpleservicetestapp"> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <application> <service android:name=".SimpleService" diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java index 4e981b22cd32..b8654d7f4e74 100644 --- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java +++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java @@ -17,8 +17,10 @@ package com.android.servicestests.apps.simpleservicetestapp; import android.app.Service; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.PackageManager; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; @@ -33,6 +35,9 @@ public class SimpleService extends Service { private static final String TEST_CLASS = "com.android.servicestests.apps.simpleservicetestapp.SimpleService"; + private static final String ACTION_SERVICE_WITH_DEP_PKG = + "com.android.servicestests.apps.simpleservicetestapp.ACTION_SERVICE_WITH_DEP_PKG"; + private static final String EXTRA_CALLBACK = "callback"; private static final String EXTRA_COMMAND = "command"; private static final String EXTRA_FLAGS = "flags"; @@ -118,6 +123,21 @@ public class SimpleService extends Service { @Override public IBinder onBind(Intent intent) { + if (ACTION_SERVICE_WITH_DEP_PKG.equals(intent.getAction())) { + final String targetPkg = intent.getStringExtra(EXTRA_TARGET_PACKAGE); + Log.i(TAG, "SimpleService.onBind: " + ACTION_SERVICE_WITH_DEP_PKG + " " + targetPkg); + if (targetPkg != null) { + Context pkgContext = null; + try { + pkgContext = createPackageContext(targetPkg, + Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable to create package context for " + pkgContext, e); + } + // This effectively loads the target package as a dependency. + pkgContext.getClassLoader(); + } + } return mBinder; } } diff --git a/services/tests/servicestests/test-apps/StubApp/Android.bp b/services/tests/servicestests/test-apps/StubApp/Android.bp new file mode 100644 index 000000000000..99deb3f5bbf0 --- /dev/null +++ b/services/tests/servicestests/test-apps/StubApp/Android.bp @@ -0,0 +1,37 @@ +// Copyright (C) 2021 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "StubTestApp", + + sdk_version: "current", + + srcs: ["**/*.java"], + + dex_preopt: { + enabled: false, + }, + optimize: { + enabled: false, + }, +} diff --git a/services/tests/servicestests/test-apps/StubApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/StubApp/AndroidManifest.xml new file mode 100644 index 000000000000..90172e77f958 --- /dev/null +++ b/services/tests/servicestests/test-apps/StubApp/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.servicestests.apps.stubapp"> + + <application android:label="StubTestApp"> + <activity android:name=".TestActivity" + android:exported="true" /> + </application> + +</manifest>
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/StubApp/src/com/android/servicestests/apps/stubapp/TestActivity.java b/services/tests/servicestests/test-apps/StubApp/src/com/android/servicestests/apps/stubapp/TestActivity.java new file mode 100644 index 000000000000..0d94676aeb52 --- /dev/null +++ b/services/tests/servicestests/test-apps/StubApp/src/com/android/servicestests/apps/stubapp/TestActivity.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 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.servicestests.apps.stubapp; + +import android.app.Activity; + +public class TestActivity extends Activity { +} diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index 4b3771b95c05..f21991defbec 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -475,7 +475,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void requestProjection_failsForBogusPackageName() throws Exception { - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)) + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) .thenReturn(TestInjector.CALLING_UID + 1); assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder, @@ -485,7 +485,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void requestProjection_failsIfNameNotFound() throws Exception { - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)) + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) .thenThrow(new PackageManager.NameNotFoundException()); assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder, @@ -495,7 +495,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void requestProjection_failsIfNoProjectionTypes() throws Exception { - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); assertThrows(IllegalArgumentException.class, () -> mService.requestProjection(mBinder, PROJECTION_TYPE_NONE, PACKAGE_NAME)); @@ -507,7 +508,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void requestProjection_failsIfMultipleProjectionTypes() throws Exception { - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); // Don't use PROJECTION_TYPE_ALL because that's actually == -1 and will fail the > 0 check. int multipleProjectionTypes = PROJECTION_TYPE_AUTOMOTIVE | 0x0002 | 0x0004; @@ -522,7 +524,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void requestProjection_enforcesToggleAutomotiveProjectionPermission() throws Exception { - doThrow(new SecurityException()).when(mPackageManager).getPackageUid(PACKAGE_NAME, 0); + doThrow(new SecurityException()) + .when(mPackageManager).getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()); assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME)); @@ -531,12 +534,14 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void requestProjection_automotive_failsIfAlreadySetByOtherPackage() throws Exception { - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes()); String otherPackage = "Raconteurs"; - when(mPackageManager.getPackageUid(otherPackage, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(otherPackage), anyInt())) + .thenReturn(TestInjector.CALLING_UID); assertFalse(mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, otherPackage)); assertThat(mService.getProjectingPackages(PROJECTION_TYPE_AUTOMOTIVE), contains(PACKAGE_NAME)); @@ -544,7 +549,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void requestProjection_failsIfCannotLinkToDeath() throws Exception { - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); doThrow(new RemoteException()).when(mBinder).linkToDeath(any(), anyInt()); assertFalse(mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME)); @@ -553,7 +559,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void requestProjection() throws Exception { - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); // Should work for all powers of two. for (int i = 0; i < Integer.SIZE; ++i) { int projectionType = 1 << i; @@ -568,11 +575,12 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void releaseProjection_failsForBogusPackageName() throws Exception { - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes()); - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)) + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) .thenReturn(TestInjector.CALLING_UID + 1); assertThrows(SecurityException.class, () -> mService.releaseProjection( @@ -582,10 +590,11 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void releaseProjection_failsIfNameNotFound() throws Exception { - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes()); - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)) + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) .thenThrow(new PackageManager.NameNotFoundException()); assertThrows(SecurityException.class, () -> mService.releaseProjection( @@ -595,7 +604,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void releaseProjection_enforcesToggleAutomotiveProjectionPermission() throws Exception { - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes()); doThrow(new SecurityException()).when(mContext).enforceCallingPermission( @@ -613,7 +623,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void releaseProjection() throws Exception { - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); requestAllPossibleProjectionTypes(); assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes()); @@ -632,7 +643,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void binderDeath_releasesProjection() throws Exception { - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); requestAllPossibleProjectionTypes(); assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes()); ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor = ArgumentCaptor.forClass( @@ -647,7 +659,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void getActiveProjectionTypes() throws Exception { assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes()); - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes()); mService.releaseProjection(PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); @@ -657,7 +670,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void getProjectingPackages() throws Exception { assertTrue(mService.getProjectingPackages(PROJECTION_TYPE_ALL).isEmpty()); - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); assertEquals(1, mService.getProjectingPackages(PROJECTION_TYPE_AUTOMOTIVE).size()); assertEquals(1, mService.getProjectingPackages(PROJECTION_TYPE_ALL).size()); @@ -681,7 +695,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void addOnProjectionStateChangedListener_callsListenerIfProjectionActive() throws Exception { - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes()); @@ -710,7 +725,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { mService.removeOnProjectionStateChangedListener(listener); // Now set automotive projection, should not call back. - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); verify(listener, never()).onProjectionStateChanged(anyInt(), any()); } @@ -726,7 +742,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { verifyNoMoreInteractions(listener); // Now set automotive projection, should call back. - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); verify(listener).onProjectionStateChanged(eq(PROJECTION_TYPE_AUTOMOTIVE), eq(List.of(PACKAGE_NAME))); @@ -752,8 +769,9 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { int fakeProjectionType = 0x0002; int otherFakeProjectionType = 0x0004; String otherPackageName = "Internet Arms"; - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); - when(mPackageManager.getPackageUid(otherPackageName, 0)) + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(otherPackageName), anyInt())) .thenReturn(TestInjector.CALLING_UID); IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class); when(listener.asBinder()).thenReturn(mBinder); // Any binder will do. @@ -806,7 +824,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { // Now kill the binder for the listener. This should remove it from the list of listeners. listenerDeathRecipient.getValue().binderDied(); - when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID); + when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) + .thenReturn(TestInjector.CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); verify(listener, never()).onProjectionStateChanged(anyInt(), any()); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index ea46eab6e8f9..d593e8000048 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -32,7 +32,6 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -40,6 +39,7 @@ import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.after; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -48,6 +48,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.Notification; @@ -73,7 +74,6 @@ import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; -import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; @@ -102,6 +102,7 @@ import java.util.Objects; @SmallTest @RunWith(AndroidJUnit4.class) +@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service. public class BuzzBeepBlinkTest extends UiServiceTestCase { @Mock AudioManager mAudioManager; @@ -156,6 +157,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); + when(mAudioManager.getFocusRampTimeMs(anyInt(), any(AudioAttributes.class))).thenReturn(50); when(mUsageStats.isAlertRateLimited(any())).thenReturn(false); when(mVibrator.hasFrequencyControl()).thenReturn(false); when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(false); @@ -444,6 +446,11 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { timeout(MAX_VIBRATION_DELAY).times(1)); } + private void verifyDelayedNeverVibrate() { + verify(mVibrator, after(MAX_VIBRATION_DELAY).never()).vibrate(anyInt(), anyString(), any(), + anyString(), any(AudioAttributes.class)); + } + private void verifyVibrate(ArgumentMatcher<VibrationEffect> effectMatcher, VerificationMode verification) { ArgumentCaptor<AudioAttributes> captor = ArgumentCaptor.forClass(AudioAttributes.class); @@ -1588,8 +1595,51 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // beep wasn't reset verifyNeverBeep(); verifyNeverVibrate(); - verify(mRingtonePlayer, never()).stopAsync(); - verify(mVibrator, never()).cancel(); + verifyNeverStopAudio(); + verifyNeverStopVibrate(); + } + + @Test + public void testRingtoneInsistentBeep_clearEffectsStopsSoundAndVibration() throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"), + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + ringtoneChannel.enableVibration(true); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + mService.addNotification(ringtoneNotification); + assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification)); + mService.buzzBeepBlinkLocked(ringtoneNotification); + verifyBeepLooped(); + verifyDelayedVibrateLooped(); + + mService.clearSoundLocked(); + mService.clearVibrateLocked(); + + verifyStopAudio(); + verifyStopVibrate(); + } + + @Test + public void testRingtoneInsistentBeep_neverVibratesWhenEffectsClearedBeforeDelay() + throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"), + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + ringtoneChannel.enableVibration(true); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + mService.addNotification(ringtoneNotification); + assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification)); + mService.buzzBeepBlinkLocked(ringtoneNotification); + verifyBeepLooped(); + verifyNeverVibrate(); + + mService.clearSoundLocked(); + mService.clearVibrateLocked(); + + verifyStopAudio(); + verifyDelayedNeverVibrate(); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 987236c7c98c..c337ccd67db8 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -379,6 +379,7 @@ public class ManagedServicesTest extends UiServiceTestCase { /** Test that restore correctly parses the user_set attribute. */ @Test public void testReadXml_restoresUserSet() throws Exception { + mVersionString = "4"; for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices( @@ -1513,7 +1514,8 @@ public class ManagedServicesTest extends UiServiceTestCase { for (int userId : mExpectedPrimary.get(service.mApprovalLevel).keySet()) { String pkgOrCmp = mExpectedPrimary.get(service.mApprovalLevel).get(userId); xml.append(getXmlEntry( - pkgOrCmp, userId, true, !(pkgOrCmp.startsWith("non.user.set.package")))); + pkgOrCmp, userId, true, + !(pkgOrCmp.startsWith("non.user.set.package")))); } for (int userId : mExpectedSecondary.get(service.mApprovalLevel).keySet()) { xml.append(getXmlEntry( @@ -1541,7 +1543,9 @@ public class ManagedServicesTest extends UiServiceTestCase { private TypedXmlPullParser getParserWithEntries(ManagedServices service, String... xmlEntries) throws Exception { final StringBuffer xml = new StringBuffer(); - xml.append("<" + service.getConfig().xmlTag + ">\n"); + xml.append("<" + service.getConfig().xmlTag + + (mVersionString != null ? " version=\"" + mVersionString + "\" " : "") + + ">\n"); for (String xmlEntry : xmlEntries) { xml.append(xmlEntry); } @@ -1726,12 +1730,19 @@ public class ManagedServicesTest extends UiServiceTestCase { } private String getXmlEntry(String approved, int userId, boolean isPrimary, boolean userSet) { + String userSetString = ""; + if (mVersionString.equals("4")) { + userSetString = + ManagedServices.ATT_USER_CHANGED + "=\"" + String.valueOf(userSet) + "\" "; + } else if (mVersionString.equals("3")) { + userSetString = + ManagedServices.ATT_USER_SET + "=\"" + (userSet ? approved : "") + "\" "; + } return "<" + ManagedServices.TAG_MANAGED_SERVICES + " " + ManagedServices.ATT_USER_ID + "=\"" + userId +"\" " + ManagedServices.ATT_IS_PRIMARY + "=\"" + isPrimary +"\" " + ManagedServices.ATT_APPROVED_LIST + "=\"" + approved +"\" " - + ManagedServices.ATT_USER_SET + "=\"" + (userSet ? approved : "") + "\" " - + "/>\n"; + + userSetString + "/>\n"; } class TestManagedServices extends ManagedServices { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java index 054a401d41af..4b93e35e673a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -16,6 +16,7 @@ package com.android.server.notification; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; @@ -124,6 +125,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase { profileIds.add(12); when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds); when(mNm.isNASMigrationDone(anyInt())).thenReturn(true); + when(mNm.canUseManagedServices(any(), anyInt(), any())).thenReturn(true); } @Test @@ -178,6 +180,92 @@ public class NotificationAssistantsTest extends UiServiceTestCase { } @Test + public void testReadXml_upgradeUserSet_preS_VersionThree() throws Exception { + String xml = "<enabled_assistants version=\"3\" defaults=\"b/b\">" + + "<service_listing approved=\"\" user=\"0\" primary=\"true\"" + + "user_set=\"true\"/>" + + "</enabled_assistants>"; + + final TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml.toString().getBytes())), null); + TriPredicate<String, Integer, String> allowedManagedServicePackages = + mNm::canUseManagedServices; + + parser.nextTag(); + mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL); + + verify(mAssistants, times(0)).upgradeUserSet(); + assertTrue(isUserSetServicesEmpty(mAssistants, 0)); + assertTrue(mAssistants.mIsUserChanged.get(0)); + } + + @Test + public void testReadXml_upgradeUserSet_preS_VersionOne() throws Exception { + String xml = "<enabled_assistants version=\"1\" defaults=\"b/b\">" + + "<service_listing approved=\"\" user=\"0\" primary=\"true\"" + + "user_set=\"true\"/>" + + "</enabled_assistants>"; + + final TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml.toString().getBytes())), null); + TriPredicate<String, Integer, String> allowedManagedServicePackages = + mNm::canUseManagedServices; + + parser.nextTag(); + mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL); + + verify(mAssistants, times(0)).upgradeUserSet(); + assertTrue(isUserSetServicesEmpty(mAssistants, 0)); + assertTrue(mAssistants.mIsUserChanged.get(0)); + } + + @Test + public void testReadXml_upgradeUserSet_preS_noUserSet() throws Exception { + String xml = "<enabled_assistants version=\"3\" defaults=\"b/b\">" + + "<service_listing approved=\"b/b\" user=\"0\" primary=\"true\"/>" + + "</enabled_assistants>"; + + final TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml.toString().getBytes())), null); + TriPredicate<String, Integer, String> allowedManagedServicePackages = + mNm::canUseManagedServices; + + parser.nextTag(); + mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL); + + verify(mAssistants, times(1)).upgradeUserSet(); + assertTrue(isUserSetServicesEmpty(mAssistants, 0)); + assertFalse(mAssistants.mIsUserChanged.get(0)); + } + + @Test + public void testReadXml_upgradeUserSet_preS_noUserSet_diffDefault() throws Exception { + String xml = "<enabled_assistants version=\"3\" defaults=\"a/a\">" + + "<service_listing approved=\"b/b\" user=\"0\" primary=\"true\"/>" + + "</enabled_assistants>"; + + final TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml.toString().getBytes())), null); + TriPredicate<String, Integer, String> allowedManagedServicePackages = + mNm::canUseManagedServices; + + parser.nextTag(); + mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL); + + verify(mAssistants, times(1)).upgradeUserSet(); + assertTrue(isUserSetServicesEmpty(mAssistants, 0)); + assertFalse(mAssistants.mIsUserChanged.get(0)); + assertEquals(new ArraySet<>(Arrays.asList(new ComponentName("a", "a"))), + mAssistants.getDefaultComponents()); + assertEquals(Arrays.asList(new ComponentName("b", "b")), + mAssistants.getAllowedComponents(0)); + } + + @Test public void testReadXml_multiApproved() throws Exception { String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">" + "<service_listing approved=\"a/a:b/b\" user=\"0\" primary=\"true\"" @@ -210,7 +298,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase { verify(mNm, never()).setDefaultAssistantForUser(anyInt()); verify(mAssistants, times(1)).addApprovedList( - new ComponentName("b", "b").flattenToString(), 10, true, null); + new ComponentName("b", "b").flattenToString(), 10, true, ""); } @Test @@ -380,4 +468,11 @@ public class NotificationAssistantsTest extends UiServiceTestCase { verify(mNm, times(1)).setDefaultAssistantForUser(eq(mZero.id)); assertEquals(new ArraySet<>(), mAssistants.getDefaultComponents()); } + + // Helper function to hold mApproved lock, avoid GuardedBy lint errors + private boolean isUserSetServicesEmpty(NotificationAssistants assistant, int userId) { + synchronized (assistant.mApproved) { + return assistant.mUserSetServices.get(userId).isEmpty(); + } + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index 577e36c7d5db..a834e2b6cc5a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -118,7 +118,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions()); assertEquals(getSmartReplies(key, i), ranking.getSmartReplies()); assertEquals(canBubble(i), ranking.canBubble()); - assertEquals(visuallyInterruptive(i), ranking.visuallyInterruptive()); + assertEquals(isTextChanged(i), ranking.isTextChanged()); assertEquals(isConversation(i), ranking.isConversation()); assertEquals(getShortcutInfo(i).getId(), ranking.getConversationShortcutInfo().getId()); assertEquals(getRankingAdjustment(i), ranking.getRankingAdjustment()); @@ -189,7 +189,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { (ArrayList) tweak.getSmartActions(), (ArrayList) tweak.getSmartReplies(), tweak.canBubble(), - tweak.visuallyInterruptive(), + tweak.isTextChanged(), tweak.isConversation(), tweak.getConversationShortcutInfo(), tweak.getRankingAdjustment(), @@ -270,7 +270,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { getSmartActions(key, i), getSmartReplies(key, i), canBubble(i), - visuallyInterruptive(i), + isTextChanged(i), isConversation(i), getShortcutInfo(i), getRankingAdjustment(i), @@ -379,7 +379,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { return index % 4 == 0; } - private boolean visuallyInterruptive(int index) { + private boolean isTextChanged(int index) { return index % 4 == 0; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 1ae219db7726..e98d077836e0 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -61,10 +61,6 @@ import static android.service.notification.NotificationListenerService.FLAG_FILT import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; -import static com.android.server.notification.NotificationManagerService.ACTION_DISABLE_NAS; -import static com.android.server.notification.NotificationManagerService.ACTION_ENABLE_NAS; -import static com.android.server.notification.NotificationManagerService.ACTION_LEARNMORE_NAS; - import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -97,6 +93,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; @@ -225,12 +222,15 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper +@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service. public class NotificationManagerServiceTest extends UiServiceTestCase { private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"; private static final int UID_HEADLESS = 1000000; @@ -325,7 +325,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock MultiRateLimiter mToastRateLimiter; BroadcastReceiver mPackageIntentReceiver; - BroadcastReceiver mNASIntentReceiver; NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( 1 << 30); @@ -553,14 +552,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { && filter.hasAction(Intent.ACTION_PACKAGES_UNSUSPENDED) && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) { mPackageIntentReceiver = broadcastReceivers.get(i); - } else if (filter.hasAction(ACTION_ENABLE_NAS) - && filter.hasAction(ACTION_DISABLE_NAS) - && filter.hasAction(ACTION_LEARNMORE_NAS)) { - mNASIntentReceiver = broadcastReceivers.get(i); } } assertNotNull("package intent receiver should exist", mPackageIntentReceiver); - assertNotNull("nas intent receiver should exist", mNASIntentReceiver); // Pretend the shortcut exists List<ShortcutInfo> shortcutInfos = new ArrayList<>(); @@ -655,16 +649,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mPackageIntentReceiver.onReceive(getContext(), intent); } - private void simulateNASUpgradeBroadcast(String action, int uid) { - final Bundle extras = new Bundle(); - extras.putInt(Intent.EXTRA_USER_ID, uid); - - final Intent intent = new Intent(action); - intent.putExtras(extras); - - mNASIntentReceiver.onReceive(getContext(), intent); - } - private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() { ArrayMap<Boolean, ArrayList<ComponentName>> changed = new ArrayMap<>(); changed.put(true, new ArrayList<>()); @@ -2460,7 +2444,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(associations); NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c"); mService.setPreferencesHelper(mPreferencesHelper); - when(mPreferencesHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt())) + when(mPreferencesHelper.getNotificationChannelGroupWithChannels( + eq(PKG), anyInt(), eq(ncg.getId()), anyBoolean())) .thenReturn(ncg); reset(mListeners); mBinderService.deleteNotificationChannelGroup(PKG, ncg.getId()); @@ -2470,6 +2455,56 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testDeleteChannelGroupChecksForFgses() throws Exception { + List<String> associations = new ArrayList<>(); + associations.add("a"); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); + CountDownLatch latch = new CountDownLatch(2); + mService.createNotificationChannelGroup( + PKG, mUid, new NotificationChannelGroup("group", "group"), true, false); + new Thread(() -> { + NotificationChannel notificationChannel = new NotificationChannel("id", "id", + NotificationManager.IMPORTANCE_HIGH); + notificationChannel.setGroup("group"); + ParceledListSlice<NotificationChannel> pls = + new ParceledListSlice(ImmutableList.of(notificationChannel)); + try { + mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + latch.countDown(); + }).start(); + new Thread(() -> { + try { + synchronized (this) { + wait(5000); + } + mService.createNotificationChannelGroup(PKG, mUid, + new NotificationChannelGroup("new", "new group"), true, false); + NotificationChannel notificationChannel = + new NotificationChannel("id", "id", NotificationManager.IMPORTANCE_HIGH); + notificationChannel.setGroup("new"); + ParceledListSlice<NotificationChannel> pls = + new ParceledListSlice(ImmutableList.of(notificationChannel)); + try { + mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls); + mBinderService.deleteNotificationChannelGroup(PKG, "group"); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } catch (Exception e) { + e.printStackTrace(); + } + latch.countDown(); + }).start(); + + latch.await(); + verify(mAmi).hasForegroundServiceNotification(anyString(), anyInt(), anyString()); + } + + @Test public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); List<String> associations = new ArrayList<>(); @@ -3872,6 +3907,40 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testTextChangedSet_forNewNotifs() throws Exception { + NotificationRecord original = generateNotificationRecord(mTestNotificationChannel); + mService.addEnqueuedNotification(original); + + NotificationManagerService.PostNotificationRunnable runnable = + mService.new PostNotificationRunnable(original.getKey()); + runnable.run(); + waitForIdle(); + + assertTrue(original.isTextChanged()); + } + + @Test + public void testVisuallyInterruptive_notSeen() throws Exception { + NotificationRecord original = generateNotificationRecord(mTestNotificationChannel); + mService.addNotification(original); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, original.getSbn().getId(), + original.getSbn().getTag(), mUid, 0, + new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setContentTitle("new title").build(), + UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord update = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + mService.addEnqueuedNotification(update); + + NotificationManagerService.PostNotificationRunnable runnable = + mService.new PostNotificationRunnable(update.getKey()); + runnable.run(); + waitForIdle(); + + assertFalse(update.isInterruptive()); + } + + @Test public void testApplyAdjustmentMultiUser() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addNotification(r); @@ -6008,7 +6077,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testNASSettingUpgrade_userSetNull_noOnBoarding() throws RemoteException { + public void testNASSettingUpgrade_userSetNull() throws RemoteException { ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component1"); TestableNotificationManagerService service = spy(mService); int userId = 11; @@ -6021,14 +6090,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(new ArrayList<>()); when(mAssistants.hasUserSet(userId)).thenReturn(true); - service.migrateDefaultNASShowNotificationIfNecessary(); + service.migrateDefaultNAS(); assertTrue(service.isNASMigrationDone(userId)); - verify(service, times(0)).createNASUpgradeNotification(eq(userId)); verify(mAssistants, times(1)).clearDefaults(); } @Test - public void testNASSettingUpgrade_userSetSameDefault_noOnBoarding() throws RemoteException { + public void testNASSettingUpgrade_userSet() throws RemoteException { ComponentName defaultComponent = ComponentName.unflattenFromString("package/Component1"); TestableNotificationManagerService service = spy(mService); int userId = 11; @@ -6041,55 +6109,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(new ArrayList(Arrays.asList(defaultComponent))); when(mAssistants.hasUserSet(userId)).thenReturn(true); - service.migrateDefaultNASShowNotificationIfNecessary(); - assertTrue(service.isNASMigrationDone(userId)); - verify(service, times(0)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(1)).resetDefaultFromConfig(); - } - - @Test - public void testNASSettingUpgrade_userSetDifferentDefault_showOnboarding() - throws RemoteException { - ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1"); - ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component2"); - TestableNotificationManagerService service = spy(mService); - int userId = 11; - setUsers(new int[]{userId}); - setNASMigrationDone(false, userId); - when(mAssistants.getDefaultComponents()) - .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent))); - when(mAssistants.getDefaultFromConfig()) - .thenReturn(newDefaultComponent); - when(mAssistants.getAllowedComponents(anyInt())) - .thenReturn(Arrays.asList(oldDefaultComponent)); - when(mAssistants.hasUserSet(userId)).thenReturn(true); - - service.migrateDefaultNASShowNotificationIfNecessary(); - assertFalse(service.isNASMigrationDone(userId)); - //TODO(b/192450820) - //verify(service, times(1)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(0)).resetDefaultFromConfig(); - - //Test user clear data before enable/disable from onboarding notification - ArrayMap<Boolean, ArrayList<ComponentName>> changedListeners = - generateResetComponentValues(); - when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changedListeners); - ArrayMap<Boolean, ArrayList<ComponentName>> changes = new ArrayMap<>(); - changes.put(true, new ArrayList(Arrays.asList(newDefaultComponent))); - changes.put(false, new ArrayList()); - when(mAssistants.resetComponents(anyString(), anyInt())).thenReturn(changes); - - //Clear data - service.getBinderService().clearData("package", userId, false); - //Test migrate flow again - service.migrateDefaultNASShowNotificationIfNecessary(); - - //The notification should be still there - assertFalse(service.isNASMigrationDone(userId)); - //TODO(b/192450820) - //verify(service, times(2)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(0)).resetDefaultFromConfig(); - assertEquals(oldDefaultComponent, service.getApprovedAssistant(userId)); + service.migrateDefaultNAS(); + verify(mAssistants, times(1)).setUserSet(userId, false); + //resetDefaultAssistantsIfNecessary should invoke from readPolicyXml() and migration + verify(mAssistants, times(2)).resetDefaultAssistantsIfNecessary(); } @Test @@ -6109,24 +6132,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent))); when(mAssistants.getDefaultFromConfig()) .thenReturn(newDefaultComponent); - //User1: need onboarding + //User1: set different NAS when(mAssistants.getAllowedComponents(userId1)) .thenReturn(Arrays.asList(oldDefaultComponent)); - //User2: no onboarding + //User2: set to none when(mAssistants.getAllowedComponents(userId2)) - .thenReturn(Arrays.asList(newDefaultComponent)); + .thenReturn(new ArrayList<>()); when(mAssistants.hasUserSet(userId1)).thenReturn(true); when(mAssistants.hasUserSet(userId2)).thenReturn(true); - service.migrateDefaultNASShowNotificationIfNecessary(); - assertFalse(service.isNASMigrationDone(userId1)); + service.migrateDefaultNAS(); + // user1's setting get reset + verify(mAssistants, times(1)).setUserSet(userId1, false); + verify(mAssistants, times(0)).setUserSet(eq(userId2), anyBoolean()); assertTrue(service.isNASMigrationDone(userId2)); - //TODO(b/192450820) - //verify(service, times(1)).createNASUpgradeNotification(any(Integer.class)); - // only user2's default get updated - verify(mAssistants, times(1)).resetDefaultFromConfig(); } @Test @@ -6146,7 +6167,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent))); when(mAssistants.getDefaultFromConfig()) .thenReturn(newDefaultComponent); - //Both profiles: need onboarding + //Both profiles: set different NAS when(mAssistants.getAllowedComponents(userId1)) .thenReturn(Arrays.asList(oldDefaultComponent)); when(mAssistants.getAllowedComponents(userId2)) @@ -6155,13 +6176,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.hasUserSet(userId1)).thenReturn(true); when(mAssistants.hasUserSet(userId2)).thenReturn(true); - service.migrateDefaultNASShowNotificationIfNecessary(); + service.migrateDefaultNAS(); assertFalse(service.isNASMigrationDone(userId1)); assertFalse(service.isNASMigrationDone(userId2)); - - // TODO(b/192450820): only user1 get notification - //verify(service, times(1)).createNASUpgradeNotification(eq(userId1)); - //verify(service, times(0)).createNASUpgradeNotification(eq(userId2)); } @@ -6189,79 +6206,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { //Clear data service.getBinderService().clearData("package", userId, false); //Test migrate flow again - service.migrateDefaultNASShowNotificationIfNecessary(); - - //TODO(b/192450820): The notification should not appear again - //verify(service, times(0)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(0)).resetDefaultFromConfig(); - } - - @Test - public void testNASUpgradeNotificationDisableBroadcast_multiProfile() { - int userId1 = 11; - int userId2 = 12; - setUsers(new int[]{userId1, userId2}); - when(mUm.isManagedProfile(userId2)).thenReturn(true); - when(mUm.getProfileIds(userId1, false)).thenReturn(new int[]{userId1, userId2}); - - TestableNotificationManagerService service = spy(mService); - setNASMigrationDone(false, userId1); - setNASMigrationDone(false, userId2); - - simulateNASUpgradeBroadcast(ACTION_DISABLE_NAS, userId1); - - assertTrue(service.isNASMigrationDone(userId1)); - assertTrue(service.isNASMigrationDone(userId2)); - // User disabled the NAS from notification, the default stored in xml should be null - // rather than the new default - verify(mAssistants, times(1)).clearDefaults(); - verify(mAssistants, times(0)).resetDefaultFromConfig(); - - //TODO(b/192450820):No more notification after disabled - //service.migrateDefaultNASShowNotificationIfNecessary(); - //verify(service, times(0)).createNASUpgradeNotification(anyInt()); - } - - @Test - public void testNASUpgradeNotificationEnableBroadcast_multiUser() { - int userId1 = 11; - int userId2 = 12; - setUsers(new int[]{userId1, userId2}); - when(mUm.getProfileIds(userId1, false)).thenReturn(new int[]{userId1}); - - TestableNotificationManagerService service = spy(mService); - setNASMigrationDone(false, userId1); - setNASMigrationDone(false, userId2); + service.migrateDefaultNAS(); - simulateNASUpgradeBroadcast(ACTION_ENABLE_NAS, userId1); - - assertTrue(service.isNASMigrationDone(userId1)); - assertFalse(service.isNASMigrationDone(userId2)); - verify(mAssistants, times(1)).resetDefaultFromConfig(); + //Migration should not happen again + verify(mAssistants, times(0)).setUserSet(userId, false); + verify(mAssistants, times(0)).clearDefaults(); + //resetDefaultAssistantsIfNecessary should only invoke once from readPolicyXml() + verify(mAssistants, times(1)).resetDefaultAssistantsIfNecessary(); - //TODO(b/192450820) - //service.migrateDefaultNASShowNotificationIfNecessary(); - //verify(service, times(0)).createNASUpgradeNotification(eq(userId1)); } - @Test - public void testNASUpgradeNotificationLearnMoreBroadcast() { - int userId = 11; - setUsers(new int[]{userId}); - TestableNotificationManagerService service = spy(mService); - setNASMigrationDone(false, userId); - doNothing().when(mContext).startActivity(any()); - - simulateNASUpgradeBroadcast(ACTION_LEARNMORE_NAS, userId); - - verify(mContext, times(1)).startActivity(any(Intent.class)); - assertFalse(service.isNASMigrationDone(userId)); - //TODO(b/192450820) - //verify(service, times(0)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(0)).resetDefaultFromConfig(); - } - - private void setNASMigrationDone(boolean done, int userId) { Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.NAS_SETTINGS_UPDATED, done ? 1 : 0, userId); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 66d157708332..77612b9d8cef 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -2224,6 +2224,19 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testIsGroupBlocked_appCannotCreateAsBlocked() throws Exception { + NotificationChannelGroup group = new NotificationChannelGroup("id", "name"); + group.setBlocked(true); + mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true); + assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId())); + + NotificationChannelGroup group3 = group.clone(); + group3.setBlocked(false); + mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group3, true); + assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId())); + } + + @Test public void testIsGroup_appCannotResetBlock() throws Exception { NotificationChannelGroup group = new NotificationChannelGroup("id", "name"); mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true); @@ -3615,7 +3628,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testGetConversations_noDisabledGroups() { NotificationChannelGroup group = new NotificationChannelGroup("a", "a"); group.setBlocked(true); - mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, false); NotificationChannel parent = new NotificationChannel("parent", "p", 1); mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java index c77a474e032c..8bc0c6c65fa3 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java @@ -40,7 +40,10 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class VibratorHelperTest extends UiServiceTestCase { + // OFF/ON vibration pattern private static final long[] CUSTOM_PATTERN = new long[] { 100, 200, 300, 400 }; + // (amplitude, frequency, duration) triples list + private static final float[] PWLE_PATTERN = new float[] { 1, 0, 100 }; @Mock private Vibrator mVibrator; @@ -58,12 +61,16 @@ public class VibratorHelperTest extends UiServiceTestCase { public void createWaveformVibration_insistent_createsRepeatingVibration() { assertRepeatingVibration( VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ true)); + assertRepeatingVibration( + VibratorHelper.createPwleWaveformVibration(PWLE_PATTERN, /* insistent= */ true)); } @Test public void createWaveformVibration_nonInsistent_createsSingleShotVibration() { assertSingleVibration( VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ false)); + assertSingleVibration( + VibratorHelper.createPwleWaveformVibration(PWLE_PATTERN, /* insistent= */ false)); } @Test @@ -71,6 +78,11 @@ public class VibratorHelperTest extends UiServiceTestCase { assertNull(VibratorHelper.createWaveformVibration(null, false)); assertNull(VibratorHelper.createWaveformVibration(new long[0], false)); assertNull(VibratorHelper.createWaveformVibration(new long[] { 0, 0 }, false)); + + assertNull(VibratorHelper.createPwleWaveformVibration(null, false)); + assertNull(VibratorHelper.createPwleWaveformVibration(new float[0], false)); + assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0 }, false)); + assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0, 0, 0 }, false)); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index 32a4774ca4f2..d4d8b86850c6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -27,6 +27,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMor import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.argThat; @@ -44,6 +45,7 @@ import android.os.IBinder; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; +import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; @@ -69,6 +71,7 @@ import java.util.function.ToIntFunction; @Presubmit @RunWith(WindowTestRunner.class) public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { + private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5); private ActivityMetricsLogger mActivityMetricsLogger; private ActivityMetricsLogger.LaunchingState mLaunchingState; private ActivityMetricsLaunchObserver mLaunchObserver; @@ -136,7 +139,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // messages that are waiting for the lock. waitHandlerIdle(mAtm.mH); // AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout. - return verify(mock, timeout(TimeUnit.SECONDS.toMillis(5))); + return verify(mock, timeout(TIMEOUT_MS)); } private void verifyOnActivityLaunchFinished(ActivityRecord activity) { @@ -257,15 +260,40 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { @Test public void testOnActivityLaunchWhileSleeping() { - notifyActivityLaunching(mTopActivity.intent); - notifyActivityLaunched(START_SUCCESS, mTopActivity); - doReturn(true).when(mTopActivity.mDisplayContent).isSleeping(); - mTopActivity.setState(Task.ActivityState.RESUMED, "test"); - mTopActivity.setVisibility(false); + notifyActivityLaunching(mTrampolineActivity.intent); + notifyActivityLaunched(START_SUCCESS, mTrampolineActivity); + doReturn(true).when(mTrampolineActivity.mDisplayContent).isSleeping(); + mTrampolineActivity.setState(ActivityRecord.State.RESUMED, "test"); + mTrampolineActivity.setVisibility(false); waitHandlerIdle(mAtm.mH); // Not cancel immediately because in one of real cases, the keyguard may be going away or // occluded later, then the activity can be drawn. - verify(mLaunchObserver, never()).onActivityLaunchCancelled(eqProto(mTopActivity)); + verify(mLaunchObserver, never()).onActivityLaunchCancelled(eqProto(mTrampolineActivity)); + + clearInvocations(mLaunchObserver); + mLaunchTopByTrampoline = true; + mTopActivity.mVisibleRequested = false; + notifyActivityLaunching(mTopActivity.intent); + // It should schedule a message with UNKNOWN_VISIBILITY_CHECK_DELAY_MS to check whether + // the launch event is still valid. + notifyActivityLaunched(START_SUCCESS, mTopActivity); + + // The posted message will acquire wm lock, so the test needs to release the lock to verify. + final Throwable error = awaitInWmLock(() -> { + try { + // Though the aborting target should be eqProto(mTopActivity), use any() to avoid + // any changes in proto that may cause failure by different arguments. + verify(mLaunchObserver, timeout(TIMEOUT_MS)).onActivityLaunchCancelled(any()); + } catch (Throwable e) { + // Catch any errors including assertion because this runs in another thread. + return e; + } + return null; + }); + // The launch event must be cancelled because the activity keeps invisible. + if (error != null) { + throw new AssertionError(error); + } } @Test @@ -376,6 +404,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // Another round without setting visibility of the trampoline activity. onActivityLaunchedTrampoline(); + mTrampolineActivity.setState(ActivityRecord.State.PAUSING, "test"); notifyWindowsDrawn(mTopActivity); // If the transition can start, the invisible activities should be discarded and the launch // event be reported successfully. @@ -447,10 +476,21 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { } @Test + public void testConsecutiveLaunch() { + onActivityLaunched(mTrampolineActivity); + mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent, + mTrampolineActivity /* caller */, mTrampolineActivity.getUid()); + notifyActivityLaunched(START_SUCCESS, mTopActivity); + transitToDrawnAndVerifyOnLaunchFinished(mTopActivity); + } + + @Test public void testConsecutiveLaunchNewTask() { final IBinder launchCookie = mock(IBinder.class); + final WindowContainerToken launchRootTask = mock(WindowContainerToken.class); mTrampolineActivity.noDisplay = true; mTrampolineActivity.mLaunchCookie = launchCookie; + mTrampolineActivity.mLaunchRootTask = launchRootTask; onActivityLaunched(mTrampolineActivity); final ActivityRecord activityOnNewTask = new ActivityBuilder(mAtm) .setCreateTask(true) @@ -464,6 +504,10 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { mTrampolineActivity.mLaunchCookie).isNull(); assertWithMessage("The last launch task has the transferred cookie").that( activityOnNewTask.mLaunchCookie).isEqualTo(launchCookie); + assertWithMessage("Trampoline's launch root task must be transferred").that( + mTrampolineActivity.mLaunchRootTask).isNull(); + assertWithMessage("The last launch task has the transferred launch root task").that( + activityOnNewTask.mLaunchRootTask).isEqualTo(launchRootTask); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java index b4fbf5fe40eb..184ea521e828 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java @@ -110,18 +110,20 @@ public class ActivityOptionsTest { @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) { - t.setVisibility(leash, true /* visible */).apply(); + t.show(leash).apply(); } int cookieIndex = -1; if (trampoline.equals(taskInfo.baseActivity)) { cookieIndex = 0; } else if (main.equals(taskInfo.baseActivity)) { cookieIndex = 1; - mainLatch.countDown(); } if (cookieIndex >= 0) { appearedCookies[cookieIndex] = taskInfo.launchCookies.isEmpty() ? null : taskInfo.launchCookies.get(0); + if (cookieIndex == 1) { + mainLatch.countDown(); + } } } }; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 6f04f176afd8..b770b3e3a55b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -16,8 +16,10 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; @@ -37,6 +39,7 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.os.Process.NOBODY_UID; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.InsetsState.ITYPE_IME; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; @@ -67,22 +70,21 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REQUESTED; -import static com.android.server.wm.Task.ActivityState.DESTROYED; -import static com.android.server.wm.Task.ActivityState.DESTROYING; -import static com.android.server.wm.Task.ActivityState.FINISHING; -import static com.android.server.wm.Task.ActivityState.INITIALIZING; -import static com.android.server.wm.Task.ActivityState.PAUSED; -import static com.android.server.wm.Task.ActivityState.PAUSING; -import static com.android.server.wm.Task.ActivityState.RESUMED; -import static com.android.server.wm.Task.ActivityState.STARTED; -import static com.android.server.wm.Task.ActivityState.STOPPED; -import static com.android.server.wm.Task.ActivityState.STOPPING; -import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE; -import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE; -import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; +import static com.android.server.wm.ActivityRecord.State.DESTROYED; +import static com.android.server.wm.ActivityRecord.State.DESTROYING; +import static com.android.server.wm.ActivityRecord.State.FINISHING; +import static com.android.server.wm.ActivityRecord.State.INITIALIZING; +import static com.android.server.wm.ActivityRecord.State.PAUSED; +import static com.android.server.wm.ActivityRecord.State.PAUSING; +import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.ActivityRecord.State.STARTED; +import static com.android.server.wm.ActivityRecord.State.STOPPED; +import static com.android.server.wm.ActivityRecord.State.STOPPING; +import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE; +import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE; +import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM; -import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE; import static com.google.common.truth.Truth.assertThat; @@ -96,11 +98,11 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import android.app.ActivityOptions; -import android.app.WindowConfiguration; import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.DestroyActivityItem; @@ -127,6 +129,9 @@ import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner.Stub; import android.view.IWindowManager; import android.view.IWindowSession; +import android.view.InsetsSource; +import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.Surface; @@ -137,15 +142,18 @@ import android.window.TaskSnapshot; import androidx.test.filters.MediumTest; import com.android.internal.R; -import com.android.server.wm.Task.ActivityState; +import com.android.server.wm.ActivityRecord.State; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; import java.util.ArrayList; +import java.util.function.BiConsumer; +import java.util.function.Consumer; /** @@ -164,6 +172,8 @@ public class ActivityRecordTests extends WindowTestsBase { @Before public void setUp() throws Exception { setBooted(mAtm); + // Because the booted state is set, avoid starting real home if there is no task. + doReturn(false).when(mRootWindowContainer).resumeHomeActivity(any(), anyString(), any()); } private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() { @@ -172,25 +182,25 @@ public class ActivityRecordTests extends WindowTestsBase { } @Test - public void testStackCleanupOnClearingTask() { + public void testTaskFragmentCleanupOnClearingTask() { final ActivityRecord activity = createActivityWith2LevelTask(); final Task task = activity.getTask(); - final Task rootTask = activity.getRootTask(); + final TaskFragment taskFragment = activity.getTaskFragment(); activity.onParentChanged(null /*newParent*/, task); - verify(rootTask, times(1)).cleanUpActivityReferences(any()); + verify(taskFragment).cleanUpActivityReferences(any()); } @Test - public void testStackCleanupOnActivityRemoval() { + public void testTaskFragmentCleanupOnActivityRemoval() { final ActivityRecord activity = createActivityWith2LevelTask(); final Task task = activity.getTask(); - final Task rootTask = activity.getRootTask(); + final TaskFragment taskFragment = activity.getTaskFragment(); task.removeChild(activity); - verify(rootTask, times(1)).cleanUpActivityReferences(any()); + verify(taskFragment).cleanUpActivityReferences(any()); } @Test - public void testStackCleanupOnTaskRemoval() { + public void testRootTaskCleanupOnTaskRemoval() { final ActivityRecord activity = createActivityWith2LevelTask(); final Task task = activity.getTask(); final Task rootTask = activity.getRootTask(); @@ -216,7 +226,7 @@ public class ActivityRecordTests extends WindowTestsBase { public void testNoCleanupMovingActivityInSameStack() { final ActivityRecord activity = createActivityWith2LevelTask(); final Task rootTask = activity.getRootTask(); - final Task newTask = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask).build(); + final Task newTask = createTaskInRootTask(rootTask, 0 /* userId */); activity.reparent(newTask, 0, null /*reason*/); verify(rootTask, times(0)).cleanUpActivityReferences(any()); } @@ -345,7 +355,7 @@ public class ActivityRecordTests extends WindowTestsBase { public void testSetsRelaunchReason_NotDragResizing() { final ActivityRecord activity = createActivityWithTask(); final Task task = activity.getTask(); - activity.setState(Task.ActivityState.RESUMED, "Testing"); + activity.setState(RESUMED, "Testing"); task.onRequestedOverrideConfigurationChanged(task.getConfiguration()); activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), @@ -370,7 +380,7 @@ public class ActivityRecordTests extends WindowTestsBase { public void testSetsRelaunchReason_DragResizing() { final ActivityRecord activity = createActivityWithTask(); final Task task = activity.getTask(); - activity.setState(Task.ActivityState.RESUMED, "Testing"); + activity.setState(RESUMED, "Testing"); task.onRequestedOverrideConfigurationChanged(task.getConfiguration()); activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), @@ -397,7 +407,7 @@ public class ActivityRecordTests extends WindowTestsBase { public void testRelaunchClearTopWaitingTranslucent() { final ActivityRecord activity = createActivityWithTask(); final Task task = activity.getTask(); - activity.setState(Task.ActivityState.RESUMED, "Testing"); + activity.setState(RESUMED, "Testing"); task.onRequestedOverrideConfigurationChanged(task.getConfiguration()); activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), @@ -418,7 +428,7 @@ public class ActivityRecordTests extends WindowTestsBase { public void testSetsRelaunchReason_NonResizeConfigChanges() { final ActivityRecord activity = createActivityWithTask(); final Task task = activity.getTask(); - activity.setState(Task.ActivityState.RESUMED, "Testing"); + activity.setState(RESUMED, "Testing"); task.onRequestedOverrideConfigurationChanged(task.getConfiguration()); activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), @@ -466,7 +476,7 @@ public class ActivityRecordTests extends WindowTestsBase { .setCreateTask(true) .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT) .build(); - activity.setState(Task.ActivityState.RESUMED, "Testing"); + activity.setState(RESUMED, "Testing"); activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), activity.getConfiguration())); @@ -556,7 +566,7 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityRecord activity = createActivityWith2LevelTask(); final Task task = activity.getTask(); final Task rootTask = activity.getRootTask(); - rootTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + rootTask.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); final Rect stableRect = new Rect(); rootTask.mDisplayContent.getStableRect(stableRect); @@ -598,19 +608,22 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void respectRequestedOrientationForNonResizableInSplitWindows() { - final Task task = new TaskBuilder(mSupervisor) - .setCreateParentTask(true).setCreateActivity(true).build(); - final Task rootTask = task.getRootTask(); + final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea(); + spyOn(tda); + doReturn(true).when(tda).supportsNonResizableMultiWindow(); + final Task rootTask = mDisplayContent.getDefaultTaskDisplayArea().createRootTask( + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + rootTask.setBounds(0, 0, 1000, 500); final ActivityRecord activity = new ActivityBuilder(mAtm) - .setParentTask(task) + .setParentTask(rootTask) + .setCreateTask(true) .setOnTop(true) .setResizeMode(RESIZE_MODE_UNRESIZEABLE) .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) .build(); + final Task task = activity.getTask(); // Task in landscape. - rootTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - task.setBounds(0, 0, 1000, 500); assertEquals(ORIENTATION_LANDSCAPE, task.getConfiguration().orientation); // Asserts fixed orientation request is respected, and the orientation is not changed. @@ -619,7 +632,7 @@ public class ActivityRecordTests extends WindowTestsBase { // Clear size compat. activity.clearSizeCompatMode(); activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */); - activity.mDisplayContent.sendNewConfiguration(); + mDisplayContent.sendNewConfiguration(); // Relaunching the app should still respect the orientation request. assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation); @@ -629,7 +642,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testShouldMakeActive_deferredResume() { final ActivityRecord activity = createActivityWithTask(); - activity.setState(Task.ActivityState.STOPPED, "Testing"); + activity.setState(STOPPED, "Testing"); mSupervisor.beginDeferResume(); assertEquals(false, activity.shouldMakeActive(null /* activeActivity */)); @@ -645,7 +658,7 @@ public class ActivityRecordTests extends WindowTestsBase { ActivityRecord finishingActivity = new ActivityBuilder(mAtm).setTask(task).build(); finishingActivity.finishing = true; ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build(); - activity.setState(Task.ActivityState.STOPPED, "Testing"); + activity.setState(STOPPED, "Testing"); assertEquals(false, activity.shouldMakeActive(null /* activeActivity */)); } @@ -654,15 +667,16 @@ public class ActivityRecordTests extends WindowTestsBase { public void testShouldResume_stackVisibility() { final ActivityRecord activity = createActivityWithTask(); final Task task = activity.getTask(); - activity.setState(Task.ActivityState.STOPPED, "Testing"); + activity.setState(STOPPED, "Testing"); - doReturn(TASK_VISIBILITY_VISIBLE).when(task).getVisibility(null); + doReturn(TASK_FRAGMENT_VISIBILITY_VISIBLE).when(task).getVisibility(null); assertEquals(true, activity.shouldResumeActivity(null /* activeActivity */)); - doReturn(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT).when(task).getVisibility(null); + doReturn(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT) + .when(task).getVisibility(null); assertEquals(false, activity.shouldResumeActivity(null /* activeActivity */)); - doReturn(TASK_VISIBILITY_INVISIBLE).when(task).getVisibility(null); + doReturn(TASK_FRAGMENT_VISIBILITY_INVISIBLE).when(task).getVisibility(null); assertEquals(false, activity.shouldResumeActivity(null /* activeActivity */)); } @@ -670,13 +684,13 @@ public class ActivityRecordTests extends WindowTestsBase { public void testShouldResumeOrPauseWithResults() { final ActivityRecord activity = createActivityWithTask(); final Task task = activity.getTask(); - activity.setState(Task.ActivityState.STOPPED, "Testing"); + activity.setState(STOPPED, "Testing"); ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build(); activity.addResultLocked(topActivity, "resultWho", 0, 0, new Intent()); topActivity.finishing = true; - doReturn(TASK_VISIBILITY_VISIBLE).when(task).getVisibility(null); + doReturn(TASK_FRAGMENT_VISIBILITY_VISIBLE).when(task).getVisibility(null); assertEquals(true, activity.shouldResumeActivity(null /* activeActivity */)); assertEquals(false, activity.shouldPauseActivity(null /*activeActivity */)); } @@ -689,7 +703,7 @@ public class ActivityRecordTests extends WindowTestsBase { .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT) .build(); final Task task = activity.getTask(); - activity.setState(Task.ActivityState.STOPPED, "Testing"); + activity.setState(STOPPED, "Testing"); final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); try { @@ -732,7 +746,7 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityRecord activity = createActivityWithTask(); ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(activity.getTask()).build(); topActivity.setOccludesParent(false); - activity.setState(Task.ActivityState.STOPPED, "Testing"); + activity.setState(STOPPED, "Testing"); activity.setVisibility(true); activity.makeActiveIfNeeded(null /* activeActivity */); assertEquals(STARTED, activity.getState()); @@ -1016,8 +1030,8 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testFinishActivityIfPossible_nonResumedFinishCompletesImmediately() { final ActivityRecord activity = createActivityWithTask(); - final ActivityState[] states = {INITIALIZING, STARTED, PAUSED, STOPPING, STOPPED}; - for (ActivityState state : states) { + final State[] states = {INITIALIZING, STARTED, PAUSED, STOPPING, STOPPED}; + for (State state : states) { activity.finishing = false; activity.setState(state, "test"); reset(activity); @@ -1080,6 +1094,7 @@ public class ActivityRecordTests extends WindowTestsBase { */ @Test public void testFinishActivityIfPossible_nonVisibleNoAppTransition() { + registerTestTransitionPlayer(); final ActivityRecord activity = createActivityWithTask(); // Put an activity on top of test activity to make it invisible and prevent us from // accidentally resuming the topmost one again. @@ -1090,6 +1105,7 @@ public class ActivityRecordTests extends WindowTestsBase { activity.finishIfPossible("test", false /* oomAdj */); verify(activity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE)); + assertFalse(activity.inTransition()); } /** @@ -1098,11 +1114,7 @@ public class ActivityRecordTests extends WindowTestsBase { */ @Test public void testFinishActivityIfPossible_lastInTaskRequestsTransitionWithTrigger() { - // Set-up mock shell transitions - final TestTransitionPlayer testPlayer = new TestTransitionPlayer( - mAtm.getTransitionController(), mAtm.mWindowOrganizerController); - mAtm.getTransitionController().registerTransitionPlayer(testPlayer); - + final TestTransitionPlayer testPlayer = registerTestTransitionPlayer(); final ActivityRecord activity = createActivityWithTask(); activity.finishing = false; activity.mVisibleRequested = true; @@ -1114,6 +1126,29 @@ public class ActivityRecordTests extends WindowTestsBase { } /** + * Verify that when collecting activity to the existing close transition, it should not affect + * ready state. + */ + @Test + public void testFinishActivityIfPossible_collectToExistingTransition() { + final TestTransitionPlayer testPlayer = registerTestTransitionPlayer(); + final ActivityRecord activity = createActivityWithTask(); + activity.setState(PAUSED, "test"); + activity.finishIfPossible("test", false /* oomAdj */); + final Transition lastTransition = testPlayer.mLastTransit; + assertTrue(lastTransition.allReady()); + assertTrue(activity.inTransition()); + + // Collect another activity to the existing transition without changing ready state. + final ActivityRecord activity2 = createActivityRecord(activity.getTask()); + activity2.setState(PAUSING, "test"); + activity2.finishIfPossible("test", false /* oomAdj */); + assertTrue(activity2.inTransition()); + assertEquals(lastTransition, testPlayer.mLastTransit); + assertTrue(lastTransition.allReady()); + } + + /** * Verify that complete finish request for non-finishing activity is invalid. */ @Test(expected = IllegalArgumentException.class) @@ -1152,7 +1187,7 @@ public class ActivityRecordTests extends WindowTestsBase { /** * Verify that finish request won't change the state of next top activity if the current * finishing activity doesn't need to be destroyed immediately. The case is usually like - * from {@link ActivityStack#completePauseLocked(boolean, ActivityRecord)} to + * from {@link Task#completePause(boolean, ActivityRecord)} to * {@link ActivityRecord#completeFinishing(String)}, so the complete-pause should take the * responsibility to resume the next activity with updating the state. */ @@ -1398,7 +1433,7 @@ public class ActivityRecordTests extends WindowTestsBase { } private void testCompleteFinishing_ensureActivitiesVisible(boolean diffTask, - ActivityState secondActivityState) { + State secondActivityState) { final ActivityRecord activity = createActivityWithTask(); final Task task = activity.getTask(); final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build(); @@ -1450,7 +1485,8 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testDestroyIfPossible() { final ActivityRecord activity = createActivityWithTask(); - doReturn(false).when(mRootWindowContainer).resumeFocusedTasksTopActivities(); + doReturn(false).when(mRootWindowContainer) + .resumeFocusedTasksTopActivities(); activity.destroyIfPossible("test"); assertEquals(DESTROYING, activity.getState()); @@ -1472,7 +1508,8 @@ public class ActivityRecordTests extends WindowTestsBase { homeStack.removeChild(t, "test"); }, true /* traverseTopToBottom */); activity.finishing = true; - doReturn(false).when(mRootWindowContainer).resumeFocusedTasksTopActivities(); + doReturn(false).when(mRootWindowContainer) + .resumeFocusedTasksTopActivities(); // Try to destroy the last activity above the home stack. activity.destroyIfPossible("test"); @@ -1599,16 +1636,23 @@ public class ActivityRecordTests extends WindowTestsBase { } @Test - public void testRemoveImmediately() throws RemoteException { - final ActivityRecord activity = createActivityWithTask(); - final WindowProcessController wpc = activity.app; - activity.getTask().removeImmediately("test"); - - verify(mAtm.getLifecycleManager()).scheduleTransaction(any(), eq(activity.appToken), - isA(DestroyActivityItem.class)); - assertNull(activity.app); - assertEquals(DESTROYED, activity.getState()); - assertFalse(wpc.hasActivities()); + public void testRemoveImmediately() { + final Consumer<Consumer<ActivityRecord>> test = setup -> { + final ActivityRecord activity = createActivityWithTask(); + final WindowProcessController wpc = activity.app; + setup.accept(activity); + activity.getTask().removeImmediately("test"); + try { + verify(mAtm.getLifecycleManager()).scheduleTransaction(any(), eq(activity.appToken), + isA(DestroyActivityItem.class)); + } catch (RemoteException ignored) { + } + assertNull(activity.app); + assertEquals(DESTROYED, activity.getState()); + assertFalse(wpc.hasActivities()); + }; + test.accept(activity -> activity.setState(RESUMED, "test")); + test.accept(activity -> activity.finishing = true); } @Test @@ -1852,7 +1896,7 @@ public class ActivityRecordTests extends WindowTestsBase { doReturn(WindowManagerGlobal.ADD_STARTING_NOT_NEEDED).when(session).addToDisplay( any() /* window */, any() /* attrs */, anyInt() /* viewVisibility */, anyInt() /* displayId */, - any() /* requestedVisibility */, any() /* outInputChannel */, + any() /* requestedVisibilities */, any() /* outInputChannel */, any() /* outInsetsState */, any() /* outActiveControls */); mAtm.mWindowManager.mStartingSurfaceController .createTaskSnapshotSurface(activity, snapshot); @@ -1908,8 +1952,7 @@ public class ActivityRecordTests extends WindowTestsBase { assertTrue(wpc.registeredForActivityConfigChanges()); // Create a new task with custom config to reparent the activity to. - final Task newTask = - new TaskBuilder(mSupervisor).setParentTask(initialTask.getRootTask()).build(); + final Task newTask = new TaskBuilder(mSupervisor).build(); final Configuration newConfig = newTask.getConfiguration(); newConfig.densityDpi += 100; newTask.onRequestedOverrideConfigurationChanged(newConfig); @@ -1941,8 +1984,7 @@ public class ActivityRecordTests extends WindowTestsBase { .diff(wpc.getRequestedOverrideConfiguration())); // Create a new task with custom config to reparent the second activity to. - final Task newTask = - new TaskBuilder(mSupervisor).setParentTask(initialTask.getRootTask()).build(); + final Task newTask = new TaskBuilder(mSupervisor).build(); final Configuration newConfig = newTask.getConfiguration(); newConfig.densityDpi += 100; newTask.onRequestedOverrideConfigurationChanged(newConfig); @@ -2152,7 +2194,7 @@ public class ActivityRecordTests extends WindowTestsBase { assertFalse(activity.supportsPictureInPicture()); } - private void verifyProcessInfoUpdate(ActivityRecord activity, ActivityState state, + private void verifyProcessInfoUpdate(ActivityRecord activity, State state, boolean shouldUpdate, boolean activityChange) { reset(activity.app); activity.setState(state, "test"); @@ -2499,16 +2541,19 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testTransferStartingWindow() { registerTestStartingWindowOrganizer(); - final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build(); - final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true) + .setVisible(false).build(); + final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true) + .setVisible(false).build(); activity1.addStartingWindow(mPackageName, android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, false, false); waitUntilHandlersIdle(); activity2.addStartingWindow(mPackageName, - android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1.appToken.asBinder(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1, true, true, false, true, false, false); waitUntilHandlersIdle(); + assertFalse(mDisplayContent.mSkipAppTransitionAnimation); assertNoStartingWindow(activity1); assertHasStartingWindow(activity2); } @@ -2523,7 +2568,7 @@ public class ActivityRecordTests extends WindowTestsBase { // Surprise, ...! Transfer window in the middle of the creation flow. activity2.addStartingWindow(mPackageName, android.R.style.Theme, null, "Test", 0, 0, 0, 0, - activity1.appToken.asBinder(), true, true, false, + activity1, true, true, false, true, false, false); }); activity1.addStartingWindow(mPackageName, @@ -2544,7 +2589,7 @@ public class ActivityRecordTests extends WindowTestsBase { false, false); waitUntilHandlersIdle(); activity2.addStartingWindow(mPackageName, - android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1.appToken.asBinder(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1, true, true, false, true, false, false); waitUntilHandlersIdle(); assertNoStartingWindow(activity1); @@ -2590,17 +2635,17 @@ public class ActivityRecordTests extends WindowTestsBase { false /* activityCreate */, false /* suggestEmpty */); waitUntilHandlersIdle(); assertHasStartingWindow(activity); - activity.mStartingWindowState = ActivityRecord.STARTING_WINDOW_SHOWN; doCallRealMethod().when(task).startActivityLocked( any(), any(), anyBoolean(), anyBoolean(), any(), any()); // In normal case, resumeFocusedTasksTopActivities() should be called after // startActivityLocked(). So skip resumeFocusedTasksTopActivities() in ActivityBuilder. - doReturn(false).when(mRootWindowContainer).resumeFocusedTasksTopActivities(); + doReturn(false).when(mRootWindowContainer) + .resumeFocusedTasksTopActivities(); // Make mVisibleSetFromTransferredStartingWindow true. final ActivityRecord middle = new ActivityBuilder(mAtm).setTask(task).build(); task.startActivityLocked(middle, null /* focusedTopActivity */, - false /* newTask */, false /* keepCurTransition */, null /* options */, + false /* newTask */, false /* isTaskSwitch */, null /* options */, null /* sourceRecord */); middle.makeFinishingLocked(); @@ -2613,9 +2658,10 @@ public class ActivityRecordTests extends WindowTestsBase { top.setVisible(false); // The finishing middle should be able to transfer starting window to top. task.startActivityLocked(top, null /* focusedTopActivity */, - false /* newTask */, false /* keepCurTransition */, null /* options */, + false /* newTask */, false /* isTaskSwitch */, null /* options */, null /* sourceRecord */); + assertTrue(mDisplayContent.mSkipAppTransitionAnimation); assertNull(middle.mStartingWindow); assertHasStartingWindow(top); assertTrue(top.isVisible()); @@ -2650,7 +2696,7 @@ public class ActivityRecordTests extends WindowTestsBase { // Make sure the fixed rotation transform linked to activity2 when adding starting window // on activity2. topActivity.addStartingWindow(mPackageName, - android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity.appToken.asBinder(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity, false, false, false, true, false, false); waitUntilHandlersIdle(); assertTrue(topActivity.hasFixedRotationTransform()); @@ -2682,6 +2728,58 @@ public class ActivityRecordTests extends WindowTestsBase { } @Test + public void testStartingWindowInTaskFragment() { + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final WindowState startingWindow = createWindowState( + new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING), activity1); + activity1.addWindow(startingWindow); + activity1.mStartingData = mock(StartingData.class); + activity1.attachStartingWindow(startingWindow); + final Task task = activity1.getTask(); + final Rect taskBounds = task.getBounds(); + final int width = taskBounds.width(); + final int height = taskBounds.height(); + final BiConsumer<TaskFragment, Rect> fragmentSetup = (fragment, bounds) -> { + final Configuration config = fragment.getRequestedOverrideConfiguration(); + config.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + config.windowConfiguration.setBounds(bounds); + fragment.onRequestedOverrideConfigurationChanged(config); + }; + + final TaskFragment taskFragment1 = new TaskFragment( + mAtm, null /* fragmentToken */, false /* createdByOrganizer */); + fragmentSetup.accept(taskFragment1, new Rect(0, 0, width / 2, height)); + task.addChild(taskFragment1, POSITION_TOP); + + final TaskFragment taskFragment2 = new TaskFragment( + mAtm, null /* fragmentToken */, false /* createdByOrganizer */); + fragmentSetup.accept(taskFragment2, new Rect(width / 2, 0, width, height)); + task.addChild(taskFragment2, POSITION_TOP); + final ActivityRecord activity2 = new ActivityBuilder(mAtm) + .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE).build(); + activity2.mVisibleRequested = true; + taskFragment2.addChild(activity2); + assertTrue(activity2.isResizeable()); + activity1.reparent(taskFragment1, POSITION_TOP); + + verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl), + eq(task.mSurfaceControl)); + assertEquals(activity1.mStartingData, startingWindow.mStartingData); + assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent()); + assertEquals(task, activity1.mStartingData.mAssociatedTask); + assertEquals(taskFragment1.getBounds(), activity1.getBounds()); + // The activity was resized by task fragment, but starting window must still cover the task. + assertEquals(taskBounds, activity1.mStartingWindow.getBounds()); + + // The starting window is only removed when all embedded activities are drawn. + final WindowState activityWindow = mock(WindowState.class); + activity1.onFirstWindowDrawn(activityWindow); + assertNotNull(activity1.mStartingWindow); + activity2.onFirstWindowDrawn(activityWindow); + assertNull(activity1.mStartingWindow); + } + + @Test public void testTransitionAnimationBounds() { removeGlobalMinSizeRestriction(); final Task task = new TaskBuilder(mSupervisor) @@ -2716,10 +2814,36 @@ public class ActivityRecordTests extends WindowTestsBase { assertEquals(taskBounds, activity.getAnimationBounds(ROOT_TASK_CLIP_AFTER_ANIM)); assertEquals(new Point(0, 0), animationPosition); + } + + @Test + public void testTransitionAnimationBounds_returnTaskFragment() { + removeGlobalMinSizeRestriction(); + final Task task = new TaskBuilder(mSupervisor).setCreateParentTask(true).build(); + final Task rootTask = task.getRootTask(); + final TaskFragment taskFragment = createTaskFragmentWithParentTask(task, + false /* createEmbeddedTask */); + final ActivityRecord activity = taskFragment.getTopNonFinishingActivity(); + final Rect stackBounds = new Rect(0, 0, 1000, 600); + final Rect taskBounds = new Rect(100, 400, 600, 800); + final Rect taskFragmentBounds = new Rect(100, 400, 300, 800); + final Rect activityBounds = new Rect(100, 400, 300, 600); + // Set the bounds and windowing mode to window configuration directly, otherwise the + // testing setups may be discarded by configuration resolving. + rootTask.getWindowConfiguration().setBounds(stackBounds); + task.getWindowConfiguration().setBounds(taskBounds); + taskFragment.getWindowConfiguration().setBounds(taskFragmentBounds); + activity.getWindowConfiguration().setBounds(activityBounds); - // ROOT_TASK_CLIP_BEFORE_ANIM should use stack bounds since it won't be clipped later. - task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - assertEquals(rootTask.getBounds(), activity.getAnimationBounds(ROOT_TASK_CLIP_BEFORE_ANIM)); + // Check that anim bounds for freeform window match task fragment bounds + task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM); + assertEquals(taskFragment.getBounds(), activity.getAnimationBounds(ROOT_TASK_CLIP_NONE)); + + // ROOT_TASK_CLIP_AFTER_ANIM should use task fragment bounds since they will be clipped by + // bounds animation layer. + task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN); + assertEquals(taskFragment.getBounds(), + activity.getAnimationBounds(ROOT_TASK_CLIP_AFTER_ANIM)); } @Test @@ -2739,6 +2863,40 @@ public class ActivityRecordTests extends WindowTestsBase { } @Test + public void testCloseToSquareFixedOrientationPortrait() { + // create a square display + final DisplayContent squareDisplay = new TestDisplayContent.Builder(mAtm, 2000, 2000) + .setSystemDecorations(true).build(); + final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build(); + + // create a fixed portrait activity + final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).build(); + + // check that both the configuration and app bounds are portrait + assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation); + assertTrue(activity.getConfiguration().windowConfiguration.getAppBounds().width() + <= activity.getConfiguration().windowConfiguration.getAppBounds().height()); + } + + @Test + public void testCloseToSquareFixedOrientationLandscape() { + // create a square display + final DisplayContent squareDisplay = new TestDisplayContent.Builder(mAtm, 2000, 2000) + .setSystemDecorations(true).build(); + final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build(); + + // create a fixed landscape activity + final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task) + .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE).build(); + + // check that both the configuration and app bounds are landscape + assertEquals(ORIENTATION_LANDSCAPE, activity.getConfiguration().orientation); + assertTrue(activity.getConfiguration().windowConfiguration.getAppBounds().width() + > activity.getConfiguration().windowConfiguration.getAppBounds().height()); + } + + @Test public void testSetVisibility_visibleToVisible() { final ActivityRecord activity = new ActivityBuilder(mAtm) .setCreateTask(true).build(); @@ -2866,6 +3024,7 @@ public class ActivityRecordTests extends WindowTestsBase { mDisplayContent.setImeInputTarget(app); // Simulate app is closing and expect the last IME is shown and IME insets is frozen. + mDisplayContent.mOpeningApps.clear(); app.mActivityRecord.commitVisibility(false, false); app.mActivityRecord.onWindowsGone(); @@ -2884,6 +3043,136 @@ public class ActivityRecordTests extends WindowTestsBase { assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); } + @UseTestDisplay(addWindows = W_INPUT_METHOD) + @Test + public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + + InsetsSource imeSource = new InsetsSource(ITYPE_IME); + app.getInsetsState().addSource(imeSource); + mDisplayContent.setImeLayeringTarget(app); + mDisplayContent.updateImeInputAndControlTarget(app); + + InsetsState state = mDisplayContent.getInsetsPolicy().getInsetsForWindow(app); + assertFalse(state.getSource(ITYPE_IME).isVisible()); + assertTrue(state.getSource(ITYPE_IME).getFrame().isEmpty()); + + // Simulate app is closing and expect IME insets is frozen. + mDisplayContent.mOpeningApps.clear(); + app.mActivityRecord.commitVisibility(false, false); + app.mActivityRecord.onWindowsGone(); + assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Simulate app re-start input or turning screen off/on then unlocked by un-secure + // keyguard to back to the app, expect IME insets is not frozen + imeSource.setFrame(new Rect(100, 400, 500, 500)); + app.getInsetsState().addSource(imeSource); + app.getInsetsState().setSourceVisible(ITYPE_IME, true); + mDisplayContent.updateImeInputAndControlTarget(app); + assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Verify when IME is visible and the app can receive the right IME insets from policy. + makeWindowVisibleAndDrawn(app, mImeWindow); + state = mDisplayContent.getInsetsPolicy().getInsetsForWindow(app); + assertTrue(state.getSource(ITYPE_IME).isVisible()); + assertEquals(state.getSource(ITYPE_IME).getFrame(), imeSource.getFrame()); + } + + @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD}) + @Test + public void testImeInsetsFrozenFlag_noDispatchVisibleInsetsWhenAppNotRequest() + throws RemoteException { + final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1"); + final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2"); + + mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindow( + mImeWindow, null, null); + mImeWindow.getControllableInsetProvider().setServerVisible(true); + + // Simulate app2 is closing and let app1 is visible to be IME targets. + makeWindowVisibleAndDrawn(app1, mImeWindow); + mDisplayContent.setImeLayeringTarget(app1); + mDisplayContent.updateImeInputAndControlTarget(app1); + app2.mActivityRecord.commitVisibility(false, false); + + // app1 requests IME visible. + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_IME, true); + app1.setRequestedVisibilities(requestedVisibilities); + mDisplayContent.getInsetsStateController().onInsetsModified(app1); + + // Verify app1's IME insets is visible and app2's IME insets frozen flag set. + assertTrue(app1.getInsetsState().peekSource(ITYPE_IME).isVisible()); + assertTrue(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Simulate switching to app2 to make it visible to be IME targets. + makeWindowVisibleAndDrawn(app2); + spyOn(app2); + spyOn(app2.mClient); + ArgumentCaptor<InsetsState> insetsStateCaptor = ArgumentCaptor.forClass(InsetsState.class); + doReturn(true).when(app2).isReadyToDispatchInsetsState(); + mDisplayContent.setImeLayeringTarget(app2); + mDisplayContent.updateImeInputAndControlTarget(app2); + + // Verify after unfreezing app2's IME insets state, we won't dispatch visible IME insets + // to client if the app didn't request IME visible. + assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput); + verify(app2.mClient, atLeastOnce()).insetsChanged(insetsStateCaptor.capture(), anyBoolean(), + anyBoolean()); + assertFalse(insetsStateCaptor.getAllValues().get(0).peekSource(ITYPE_IME).isVisible()); + } + + @Test + public void testInClosingAnimation_visibilityNotCommitted_doNotHideSurface() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + makeWindowVisibleAndDrawn(app); + + // Put the activity in close transition. + mDisplayContent.mOpeningApps.clear(); + mDisplayContent.mClosingApps.add(app.mActivityRecord); + mDisplayContent.prepareAppTransition(TRANSIT_CLOSE); + + // Remove window during transition, so it is requested to hide, but won't be committed until + // the transition is finished. + app.mActivityRecord.onRemovedFromDisplay(); + + assertTrue(mDisplayContent.mClosingApps.contains(app.mActivityRecord)); + assertFalse(app.mActivityRecord.isVisibleRequested()); + assertTrue(app.mActivityRecord.isVisible()); + assertTrue(app.mActivityRecord.isSurfaceShowing()); + + // Start transition. + app.mActivityRecord.prepareSurfaces(); + + // Because the app is waiting for transition, it should not hide the surface. + assertTrue(app.mActivityRecord.isSurfaceShowing()); + } + + @Test + public void testInClosingAnimation_visibilityCommitted_hideSurface() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + makeWindowVisibleAndDrawn(app); + + // Put the activity in close transition. + mDisplayContent.mOpeningApps.clear(); + mDisplayContent.mClosingApps.add(app.mActivityRecord); + mDisplayContent.prepareAppTransition(TRANSIT_CLOSE); + + // Commit visibility before start transition. + app.mActivityRecord.commitVisibility(false, false); + + assertFalse(app.mActivityRecord.isVisibleRequested()); + assertFalse(app.mActivityRecord.isVisible()); + assertTrue(app.mActivityRecord.isSurfaceShowing()); + + // Start transition. + app.mActivityRecord.prepareSurfaces(); + + // Because the app visibility has been committed before the transition start, it should hide + // the surface. + assertFalse(app.mActivityRecord.isSurfaceShowing()); + } + private void assertHasStartingWindow(ActivityRecord atoken) { assertNotNull(atoken.mStartingSurface); assertNotNull(atoken.mStartingData); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java index a3ad09a50b8c..c103bc6fb9a1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java @@ -28,6 +28,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; +import android.annotation.Nullable; import android.app.ActivityManagerInternal; import android.app.KeyguardManager; import android.app.admin.DevicePolicyManagerInternal; @@ -42,6 +43,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Presubmit; import android.testing.DexmakerShareClassLoaderRule; +import android.util.SparseArray; import androidx.test.filters.SmallTest; @@ -64,7 +66,7 @@ import org.mockito.MockitoAnnotations; * Unit tests for {@link ActivityStartInterceptorTest}. * * Build/Install/Run: - * atest WmTests:ActivityStartInterceptorTest + * atest WmTests:ActivityStartInterceptorTest */ @SmallTest @Presubmit @@ -114,6 +116,9 @@ public class ActivityStartInterceptorTest { private ActivityStartInterceptor mInterceptor; private ActivityInfo mAInfo = new ActivityInfo(); + private SparseArray<ActivityInterceptorCallback> mActivityInterceptorCallbacks = + new SparseArray<>(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -157,6 +162,9 @@ public class ActivityStartInterceptorTest { TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)) .thenReturn(true); + // Mock the activity start callbacks + when(mService.getActivityInterceptorCallbacks()).thenReturn(mActivityInterceptorCallbacks); + // Initialise activity info mAInfo.applicationInfo = new ApplicationInfo(); mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME; @@ -285,4 +293,38 @@ public class ActivityStartInterceptorTest { // THEN calling intercept returns false assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); } + + public void addMockInterceptorCallback(@Nullable Intent intent) { + int size = mActivityInterceptorCallbacks.size(); + mActivityInterceptorCallbacks.put(size, new ActivityInterceptorCallback() { + @Override + public Intent intercept(ActivityInterceptorInfo info) { + return intent; + } + }); + } + + @Test + public void testInterceptionCallback_singleCallback() { + addMockInterceptorCallback(new Intent("android.test.foo")); + + assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); + assertEquals("android.test.foo", mInterceptor.mIntent.getAction()); + } + + @Test + public void testInterceptionCallback_singleCallbackReturnsNull() { + addMockInterceptorCallback(null); + + assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); + } + + @Test + public void testInterceptionCallback_fallbackToSecondCallback() { + addMockInterceptorCallback(null); + addMockInterceptorCallback(new Intent("android.test.second")); + + assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); + assertEquals("android.test.second", mInterceptor.mIntent.getAction()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 18450b64c8b1..01deb5c7834c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -32,6 +32,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED; @@ -479,7 +480,7 @@ public class ActivityStarterTests extends WindowTestsBase { final ActivityRecord splitSecondActivity = new ActivityBuilder(mAtm).setCreateTask(true).build(); final ActivityRecord splitPrimaryActivity = new TaskBuilder(mSupervisor) - .setParentTask(splitOrg.mPrimary) + .setParentTaskFragment(splitOrg.mPrimary) .setCreateActivity(true) .build() .getTopMostActivity(); @@ -760,12 +761,12 @@ public class ActivityStarterTests extends WindowTestsBase { } /** - * This test ensures that {@link ActivityStarter#setTargetStackAndMoveToFrontIfNeeded} will - * move the existing task to front if the current focused stack doesn't have running task. + * This test ensures that {@link ActivityStarter#setTargetRootTaskIfNeeded} will + * move the existing task to front if the current focused root task doesn't have running task. */ @Test - public void testBringTaskToFrontWhenFocusedStackIsFinising() { - // Put 2 tasks in the same stack (simulate the behavior of home stack). + public void testBringTaskToFrontWhenFocusedTaskIsFinishing() { + // Put 2 tasks in the same root task (simulate the behavior of home root task). final Task rootTask = new TaskBuilder(mSupervisor).build(); final ActivityRecord activity = new ActivityBuilder(mAtm) .setParentTask(rootTask) @@ -782,13 +783,16 @@ public class ActivityStarterTests extends WindowTestsBase { assertEquals(finishingTopActivity, mRootWindowContainer.topRunningActivity()); finishingTopActivity.finishing = true; - // Launch the bottom task of the target stack. + // Launch the bottom task of the target root task. prepareStarter(FLAG_ACTIVITY_NEW_TASK, false /* mockGetLaunchStack */) - .setReason("testBringTaskToFrontWhenTopStackIsFinising") - .setIntent(activity.intent) + .setReason("testBringTaskToFrontWhenFocusedTaskIsFinishing") + .setIntent(activity.intent.addFlags( + FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) .execute(); + verify(activity.getRootTask()).startActivityLocked(any(), any(), anyBoolean(), + eq(true) /* isTaskSwitch */, any(), any()); // The hierarchies of the activity should move to front. - assertEquals(activity, mRootWindowContainer.topRunningActivity()); + assertEquals(activity.getTask(), mRootWindowContainer.topRunningActivity().getTask()); } /** @@ -857,7 +861,7 @@ public class ActivityStarterTests extends WindowTestsBase { // Create another activity on top of the secondary display. final Task topStack = secondaryTaskContainer.createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final Task topTask = new TaskBuilder(mSupervisor).setParentTask(topStack).build(); + final Task topTask = new TaskBuilder(mSupervisor).setParentTaskFragment(topStack).build(); new ActivityBuilder(mAtm).setTask(topTask).build(); doReturn(mActivityMetricsLogger).when(mSupervisor).getActivityMetricsLogger(); @@ -921,7 +925,7 @@ public class ActivityStarterTests extends WindowTestsBase { DEFAULT_COMPONENT_PACKAGE_NAME + ".SingleTaskActivity"); final Task task = new TaskBuilder(mSupervisor) .setComponent(componentName) - .setParentTask(stack) + .setParentTaskFragment(stack) .build(); return new ActivityBuilder(mAtm) .setComponent(componentName) @@ -1057,8 +1061,8 @@ public class ActivityStarterTests extends WindowTestsBase { final ActivityStarter starter = prepareStarter(0 /* flags */); starter.mStartActivity = new ActivityBuilder(mAtm).build(); final Task task = new TaskBuilder(mAtm.mTaskSupervisor) - .setParentTask(mAtm.mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */)) + .setParentTaskFragment(createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD)) .setUserId(10) .build(); @@ -1141,6 +1145,7 @@ public class ActivityStarterTests extends WindowTestsBase { /* doResume */true, /* options */null, /* inTask */null, + /* inTaskFragment */ null, /* restrictedBgActivity */false, /* intentGrants */null); @@ -1151,6 +1156,31 @@ public class ActivityStarterTests extends WindowTestsBase { } @Test + public void testStartActivityInner_inTaskFragment() { + final ActivityStarter starter = prepareStarter(0, false); + final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build(); + final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final TaskFragment taskFragment = new TaskFragment(mAtm, sourceRecord.token, + true /* createdByOrganizer */); + sourceRecord.getTask().addChild(taskFragment, POSITION_TOP); + + starter.startActivityInner( + /* r */targetRecord, + /* sourceRecord */ sourceRecord, + /* voiceSession */null, + /* voiceInteractor */ null, + /* startFlags */ 0, + /* doResume */true, + /* options */null, + /* inTask */null, + /* inTaskFragment */ taskFragment, + /* restrictedBgActivity */false, + /* intentGrants */null); + + assertTrue(taskFragment.hasChild()); + } + + @Test public void testLaunchCookie_newAndExistingTask() { final ActivityStarter starter = prepareStarter(0, false); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index 2de9aba7afb3..5d1a06825a75 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -26,9 +26,16 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.ActivityInterceptorCallback.FIRST_ORDERED_ID; +import static com.android.server.wm.ActivityInterceptorCallback.LAST_ORDERED_ID; +import static com.android.server.wm.ActivityRecord.State.PAUSED; +import static com.android.server.wm.ActivityRecord.State.PAUSING; +import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.ActivityRecord.State.STOPPING; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -37,15 +44,21 @@ import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; +import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; +import android.app.IApplicationThread; import android.app.PictureInPictureParams; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.EnterPipRequestedItem; +import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.Binder; import android.os.IBinder; +import android.os.LocaleList; import android.os.PowerManager; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; @@ -57,6 +70,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import org.mockito.MockitoSession; import java.util.ArrayList; @@ -76,6 +90,9 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { private final ArgumentCaptor<ClientTransaction> mClientTransactionCaptor = ArgumentCaptor.forClass(ClientTransaction.class); + private static final String DEFAULT_PACKAGE_NAME = "my.application.package"; + private static final int DEFAULT_USER_ID = 100; + @Before public void setUp() throws Exception { setBooted(mAtm); @@ -247,7 +264,7 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { .setTask(mRootWindowContainer.getDefaultTaskDisplayArea().getOrCreateRootHomeTask()) .build(); final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); - activity.setState(Task.ActivityState.RESUMED, "test"); + activity.setState(RESUMED, "test"); mSupervisor.endDeferResume(); assertEquals(activity.app, mAtm.mInternal.getTopApp()); @@ -257,13 +274,13 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { activity.mVisibleRequested = false; activity.setVisible(false); activity.getTask().setPausingActivity(activity); - homeActivity.setState(Task.ActivityState.PAUSED, "test"); + homeActivity.setState(PAUSED, "test"); // Even the visibility states are invisible, the next activity should be resumed because // the crashed activity was pausing. mAtm.mInternal.handleAppDied(activity.app, false /* restarting */, null /* finishInstrumentationCallback */); - assertEquals(Task.ActivityState.RESUMED, homeActivity.getState()); + assertEquals(RESUMED, homeActivity.getState()); assertEquals(homeActivity.app, mAtm.mInternal.getTopApp()); } @@ -274,7 +291,7 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { final Task rootHomeTask = mWm.mRoot.getDefaultTaskDisplayArea().getOrCreateRootHomeTask(); final ActivityRecord homeActivity = new ActivityBuilder(mAtm).setTask(rootHomeTask).build(); final ActivityRecord topActivity = new ActivityBuilder(mAtm).setCreateTask(true).build(); - topActivity.setState(Task.ActivityState.RESUMED, "test"); + topActivity.setState(RESUMED, "test"); final Consumer<ActivityRecord> assertTopNonSleeping = activity -> { assertFalse(mAtm.mInternal.isSleeping()); @@ -290,18 +307,27 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { verify(mSupervisor.mGoingToSleepWakeLock).acquire(); doReturn(true).when(mSupervisor.mGoingToSleepWakeLock).isHeld(); - assertEquals(Task.ActivityState.PAUSING, topActivity.getState()); + assertEquals(PAUSING, topActivity.getState()); assertTrue(mAtm.mInternal.isSleeping()); assertEquals(ActivityManager.PROCESS_STATE_TOP_SLEEPING, mAtm.mInternal.getTopProcessState()); // The top app should not change while sleeping. assertEquals(topActivity.app, mAtm.mInternal.getTopApp()); + mAtm.startLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY + | ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY); + assertEquals(ActivityManager.PROCESS_STATE_TOP, mAtm.mInternal.getTopProcessState()); + // Because there is no unknown visibility record, the state will be restored if other + // reasons are all done. + mAtm.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); + assertEquals(ActivityManager.PROCESS_STATE_TOP_SLEEPING, + mAtm.mInternal.getTopProcessState()); + // If all activities are stopped, the sleep wake lock must be released. final Task topRootTask = topActivity.getRootTask(); doReturn(true).when(rootHomeTask).goToSleepIfPossible(anyBoolean()); doReturn(true).when(topRootTask).goToSleepIfPossible(anyBoolean()); - topActivity.setState(Task.ActivityState.STOPPING, "test"); + topActivity.setState(STOPPING, "test"); topActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */, null /* description */); verify(mSupervisor.mGoingToSleepWakeLock).release(); @@ -476,5 +502,412 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { assertTrue(activity.supportsMultiWindow()); assertTrue(task.supportsMultiWindow()); } -} + @Test + public void testPackageConfigUpdate_locales_successfullyApplied() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID)); + + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit(); + + WindowProcessController wpcAfterConfigChange = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange.getConfiguration().getLocales()); + assertFalse(wpcAfterConfigChange.getConfiguration().isNightModeActive()); + } + + @Test + public void testPackageConfigUpdate_nightMode_successfullyApplied() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID)); + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + WindowProcessController wpcAfterConfigChange = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertTrue(wpcAfterConfigChange.getConfiguration().isNightModeActive()); + assertEquals(LocaleList.forLanguageTags("en-XC"), + wpcAfterConfigChange.getConfiguration().getLocales()); + } + + @Test + public void testPackageConfigUpdate_multipleLocaleUpdates_successfullyApplied() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + WindowProcessController wpc = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), wpc); + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) + .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + WindowProcessController wpcAfterConfigChange1 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange1.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpc.getConfiguration().getLocales()); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("ja-XC,en-XC")).commit(); + + WindowProcessController wpcAfterConfigChange2 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + + assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"), + wpcAfterConfigChange2.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"), + wpc.getConfiguration().getLocales()); + } + + @Test + public void testPackageConfigUpdate_multipleNightModeUpdates_successfullyApplied() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID)); + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) + .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + WindowProcessController wpcAfterConfigChange1 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange1.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + + packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_NO).commit(); + + WindowProcessController wpcAfterConfigChange2 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange2.getConfiguration().getLocales()); + assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive()); + } + + @Test + public void testPackageConfigUpdate_onPackageUninstall_configShouldNotApply() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID)); + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) + .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + WindowProcessController wpcAfterConfigChange1 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange1.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + + mAtm.mInternal.onPackageUninstalled(DEFAULT_PACKAGE_NAME); + + WindowProcessController wpcAfterConfigChange2 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XC"), + wpcAfterConfigChange2.getConfiguration().getLocales()); + assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive()); + } + + @Test + public void testPackageConfigUpdate_LocalesEmptyAndNightModeUndefined_configShouldNotApply() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + WindowProcessController wpc = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), wpc); + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) + .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + WindowProcessController wpcAfterConfigChange1 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange1.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpc.getConfiguration().getLocales()); + + packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()) + .setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit(); + + WindowProcessController wpcAfterConfigChange2 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XC"), + wpcAfterConfigChange2.getConfiguration().getLocales()); + assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive()); + assertEquals(LocaleList.forLanguageTags("en-XC"), + wpc.getConfiguration().getLocales()); + } + + @Test + public void testPackageConfigUpdate_WhenUserRemoved_configShouldNotApply() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID)); + + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) + .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + WindowProcessController wpcAfterConfigChange1 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange1.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + + mAtm.mInternal.removeUser(DEFAULT_USER_ID); + + WindowProcessController wpcAfterConfigChange2 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XC"), + wpcAfterConfigChange2.getConfiguration().getLocales()); + assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive()); + } + + @Test + public void testPackageConfigUpdate_setLocaleListToEmpty_doesNotOverlayLocaleListInWpc() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID)); + + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) + .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + WindowProcessController wpcAfterConfigChange1 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange1.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + + packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()).commit(); + + WindowProcessController wpcAfterConfigChange2 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XC"), + wpcAfterConfigChange2.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange2.getConfiguration().isNightModeActive()); + } + + @Test + public void testPackageConfigUpdate_resetNightMode_doesNotOverrideNightModeInWpc() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID)); + + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) + .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + WindowProcessController wpcAfterConfigChange1 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange1.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + + packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit(); + + WindowProcessController wpcAfterConfigChange2 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange2.getConfiguration().getLocales()); + assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive()); + } + + @Test + public void testPackageConfigUpdate_localesNotSet_localeConfigRetrievedNull() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, + DEFAULT_USER_ID); + WindowProcessController wpc = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), wpc); + mAtm.mInternal.onProcessAdded(wpc); + + ActivityTaskManagerInternal.PackageConfig appSpecificConfig = mAtm.mInternal + .getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + // when no configuration is set we get a null object. + assertNull(appSpecificConfig); + + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME, + DEFAULT_USER_ID); + packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + ActivityTaskManagerInternal.PackageConfig appSpecificConfig2 = mAtm.mInternal + .getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertNotNull(appSpecificConfig2); + assertNull(appSpecificConfig2.mLocales); + assertEquals(appSpecificConfig2.mNightMode.intValue(), Configuration.UI_MODE_NIGHT_YES); + } + + @Test + public void testPackageConfigUpdate_appNotRunning_configSuccessfullyApplied() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, + DEFAULT_USER_ID); + + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME, + DEFAULT_USER_ID); + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit(); + + // Verifies if the persisted app-specific configuration is same as the committed + // configuration. + ActivityTaskManagerInternal.PackageConfig appSpecificConfig = mAtm.mInternal + .getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertNotNull(appSpecificConfig); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB"), appSpecificConfig.mLocales); + + // Verifies if the persisted configuration for an arbitrary app is applied correctly when + // a new WindowProcessController is created for it. + WindowProcessController wpcAfterConfigChange = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange.getConfiguration().getLocales()); + } + + @Test + public void testPackageConfigUpdate_appRunning_configSuccessfullyApplied() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, + DEFAULT_USER_ID); + WindowProcessController wpc = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), wpc); + mAtm.mInternal.onProcessAdded(wpc); + + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME, + DEFAULT_USER_ID); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit(); + + ActivityTaskManagerInternal.PackageConfig appSpecificConfig = mAtm.mInternal + .getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + + // Verifies if the persisted app-specific configuration is same as the committed + // configuration. + assertNotNull(appSpecificConfig); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB"), appSpecificConfig.mLocales); + + // Verifies if the committed configuration is successfully applied to the required + // application while it is currently running. + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpc.getConfiguration().getLocales()); + } + + private WindowProcessController createWindowProcessController(String packageName, + int userId) { + WindowProcessListener mMockListener = Mockito.mock(WindowProcessListener.class); + ApplicationInfo info = mock(ApplicationInfo.class); + info.packageName = packageName; + WindowProcessController wpc = new WindowProcessController( + mAtm, info, packageName, 0, userId, null, mMockListener); + wpc.setThread(mock(IApplicationThread.class)); + return wpc; + } + + @Test(expected = IllegalArgumentException.class) + public void testRegisterActivityStartInterceptor_IndexTooSmall() { + mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID - 1, + new ActivityInterceptorCallback() { + @Nullable + @Override + public Intent intercept(ActivityInterceptorInfo info) { + return null; + } + }); + } + + @Test(expected = IllegalArgumentException.class) + public void testRegisterActivityStartInterceptor_IndexTooLarge() { + mAtm.mInternal.registerActivityStartInterceptor(LAST_ORDERED_ID + 1, + new ActivityInterceptorCallback() { + @Nullable + @Override + public Intent intercept(ActivityInterceptorInfo info) { + return null; + } + }); + } + + @Test(expected = IllegalArgumentException.class) + public void testRegisterActivityStartInterceptor_DuplicateId() { + mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID, + new ActivityInterceptorCallback() { + @Nullable + @Override + public Intent intercept(ActivityInterceptorInfo info) { + return null; + } + }); + mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID, + new ActivityInterceptorCallback() { + @Nullable + @Override + public Intent intercept(ActivityInterceptorInfo info) { + return null; + } + }); + } + + @Test + public void testRegisterActivityStartInterceptor() { + assertEquals(0, mAtm.getActivityInterceptorCallbacks().size()); + + mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID, + new ActivityInterceptorCallback() { + @Nullable + @Override + public Intent intercept(ActivityInterceptorInfo info) { + return null; + } + }); + + assertEquals(1, mAtm.getActivityInterceptorCallbacks().size()); + assertTrue(mAtm.getActivityInterceptorCallbacks().contains(FIRST_ORDERED_ID)); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java index 19f9b758811f..66da2a631868 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.ActivityManager.START_DELIVERED_TO_TOP; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; @@ -36,6 +37,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; @@ -52,6 +54,7 @@ import androidx.test.filters.MediumTest; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; import java.util.concurrent.TimeUnit; @@ -111,16 +114,18 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { final ActivityMetricsLogger.LaunchingState launchingState = new ActivityMetricsLogger.LaunchingState(); spyOn(launchingState); - doReturn(true).when(launchingState).contains(eq(secondActivity)); + doReturn(true).when(launchingState).hasActiveTransitionInfo(); + doReturn(true).when(launchingState).contains( + ArgumentMatchers.argThat(r -> r == firstActivity || r == secondActivity)); // The test case already runs inside global lock, so above thread can only execute after // this waiting method that releases the lock. mSupervisor.waitActivityVisibleOrLaunched(taskToFrontWait, firstActivity, launchingState); // Assert that the thread is finished. assertTrue(condition.block(TIMEOUT_MS)); - assertEquals(taskToFrontWait.result, START_TASK_TO_FRONT); - assertEquals(taskToFrontWait.who, secondActivity.mActivityComponent); - assertEquals(taskToFrontWait.launchState, WaitResult.LAUNCH_STATE_HOT); + assertEquals(START_TASK_TO_FRONT, taskToFrontWait.result); + assertEquals(secondActivity.mActivityComponent, taskToFrontWait.who); + assertEquals(WaitResult.LAUNCH_STATE_HOT, taskToFrontWait.launchState); // START_TASK_TO_FRONT means that another component will be visible, so the component // should not be assigned as the first activity. assertNull(launchedComponent[0]); @@ -263,6 +268,30 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { } /** + * Verifies that process state will be updated with pending top without activity state change. + * E.g. switch focus between resumed activities in multi-window mode. + */ + @Test + public void testUpdatePendingTopForTopResumed() { + final Task task1 = new TaskBuilder(mSupervisor) + .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW).build(); + final ActivityRecord activity1 = new ActivityBuilder(mAtm) + .setTask(task1).setUid(ActivityBuilder.DEFAULT_FAKE_UID + 1).build(); + task1.setResumedActivity(activity1, "test"); + + final ActivityRecord activity2 = new TaskBuilder(mSupervisor) + .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW) + .setCreateActivity(true).build().getTopMostActivity(); + activity2.getTask().setResumedActivity(activity2, "test"); + + mAtm.mAmInternal.deletePendingTopUid(activity1.getUid()); + clearInvocations(mAtm); + activity1.moveFocusableActivityToTop("test"); + assertTrue(mAtm.mAmInternal.isPendingTopUid(activity1.getUid())); + verify(mAtm).updateOomAdj(); + } + + /** * We need to launch home again after user unlocked for those displays that do not have * encryption aware home app. */ diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 3a6aac9d03d5..506270657e42 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -26,16 +26,28 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN; import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import android.annotation.Nullable; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -47,8 +59,9 @@ import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationTarget; import android.view.WindowManager; +import android.window.ITaskFragmentOrganizer; +import android.window.TaskFragmentOrganizer; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import org.junit.Before; @@ -94,11 +107,24 @@ public class AppTransitionControllerTest extends WindowTestsBase { assertEquals(WindowManager.TRANSIT_OLD_UNSET, AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null, null, false)); + mDisplayContent.mChangingContainers, null, null, false)); + } + + @Test + public void testClearTaskSkipAppExecuteTransition() { + final ActivityRecord behind = createActivityRecord(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final Task task = behind.getTask(); + final ActivityRecord top = createActivityRecord(task); + top.setState(ActivityRecord.State.RESUMED, "test"); + behind.setState(ActivityRecord.State.STARTED, "test"); + behind.mVisibleRequested = true; + + task.performClearTask("test"); + assertFalse(mDisplayContent.mAppTransition.isReady()); } @Test - @FlakyTest(bugId = 131005232) public void testTranslucentOpen() { final ActivityRecord behind = createActivityRecord(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); @@ -112,12 +138,11 @@ public class AppTransitionControllerTest extends WindowTestsBase { assertEquals(WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN, AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition, - mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null, null, false)); + mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, + mDisplayContent.mChangingContainers, null, null, false)); } @Test - @FlakyTest(bugId = 131005232) public void testTranslucentClose() { final ActivityRecord behind = createActivityRecord(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); @@ -129,11 +154,10 @@ public class AppTransitionControllerTest extends WindowTestsBase { assertEquals(WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE, AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null, null, false)); + mDisplayContent.mChangingContainers, null, null, false)); } @Test - @FlakyTest(bugId = 131005232) public void testChangeIsNotOverwritten() { final ActivityRecord behind = createActivityRecord(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); @@ -144,14 +168,14 @@ public class AppTransitionControllerTest extends WindowTestsBase { mDisplayContent.prepareAppTransition(TRANSIT_CHANGE); mDisplayContent.mOpeningApps.add(behind); mDisplayContent.mOpeningApps.add(translucentOpening); + mDisplayContent.mChangingContainers.add(translucentOpening.getTask()); assertEquals(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE, AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null, null, false)); + mDisplayContent.mChangingContainers, null, null, false)); } @Test - @FlakyTest(bugId = 131005232) public void testTransitWithinTask() { final ActivityRecord opening = createActivityRecord(mDisplayContent, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); @@ -198,7 +222,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { assertEquals(WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN, AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - appWindowClosing, null, false)); + mDisplayContent.mChangingContainers, appWindowClosing, null, false)); } @Test @@ -229,7 +253,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { assertEquals(WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN, AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - appWindowClosing, null, false)); + mDisplayContent.mChangingContainers, appWindowClosing, null, false)); } @Test @@ -310,6 +334,18 @@ public class AppTransitionControllerTest extends WindowTestsBase { } @Test + public void testExitAnimationDone_beforeAppTransition() { + final Task task = createTask(mDisplayContent); + final WindowState win = createAppWindow(task, ACTIVITY_TYPE_STANDARD, "Win"); + spyOn(win); + win.mAnimatingExit = true; + mDisplayContent.mAppTransition.setTimeout(); + mDisplayContent.mAppTransitionController.handleAppTransitionReady(); + + verify(win).onExitAnimationDone(); + } + + @Test public void testGetAnimationTargets_windowsAreBeingReplaced() { // [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, visible) // +- [AppWindow1] (being-replaced) @@ -375,7 +411,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { final ArraySet<ActivityRecord> closing = new ArraySet<>(); closing.add(activity3); - // Promote animation targets to TaskStack level. Invisible ActivityRecords don't affect + // Promote animation targets to root Task level. Invisible ActivityRecords don't affect // promotion decision. assertEquals( new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}), @@ -520,6 +556,128 @@ public class AppTransitionControllerTest extends WindowTestsBase { opening, closing, false /* visible */)); } + @Test + public void testGetAnimationTargets_openingClosingTaskFragment() { + // [DefaultTDA] - [Task] -+- [TaskFragment1] - [ActivityRecord1] (opening, invisible) + // +- [TaskFragment2] - [ActivityRecord2] (closing, visible) + final Task parentTask = createTask(mDisplayContent); + final TaskFragment taskFragment1 = createTaskFragmentWithParentTask(parentTask, + false /* createEmbeddedTask */); + final ActivityRecord activity1 = taskFragment1.getTopMostActivity(); + activity1.setVisible(false); + activity1.mVisibleRequested = true; + + final TaskFragment taskFragment2 = createTaskFragmentWithParentTask(parentTask, + false /* createEmbeddedTask */); + final ActivityRecord activity2 = taskFragment2.getTopMostActivity(); + activity2.setVisible(true); + activity2.mVisibleRequested = false; + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + closing.add(activity2); + + // Promote animation targets up to TaskFragment level, not beyond. + assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment1}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment2}), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + } + + @Test + public void testGetAnimationTargets_openingClosingTaskFragmentWithEmbeddedTask() { + // [DefaultTDA] - [Task] -+- [TaskFragment1] - [ActivityRecord1] (opening, invisible) + // +- [TaskFragment2] - [ActivityRecord2] (closing, visible) + final Task parentTask = createTask(mDisplayContent); + final TaskFragment taskFragment1 = createTaskFragmentWithParentTask(parentTask, + true /* createEmbeddedTask */); + final ActivityRecord activity1 = taskFragment1.getTopMostActivity(); + activity1.setVisible(false); + activity1.mVisibleRequested = true; + + final TaskFragment taskFragment2 = createTaskFragmentWithParentTask(parentTask, + true /* createEmbeddedTask */); + final ActivityRecord activity2 = taskFragment2.getTopMostActivity(); + activity2.setVisible(true); + activity2.mVisibleRequested = false; + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + closing.add(activity2); + + // Promote animation targets up to TaskFragment level, not beyond. + assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment1}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment2}), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + } + + @Test + public void testGetAnimationTargets_openingTheOnlyTaskFragmentInTask() { + // [DefaultTDA] -+- [Task1] - [TaskFragment1] - [ActivityRecord1] (opening, invisible) + // +- [Task2] - [ActivityRecord2] (closing, visible) + final Task task1 = createTask(mDisplayContent); + final TaskFragment taskFragment1 = createTaskFragmentWithParentTask(task1, + false /* createEmbeddedTask */); + final ActivityRecord activity1 = taskFragment1.getTopMostActivity(); + activity1.setVisible(false); + activity1.mVisibleRequested = true; + + final ActivityRecord activity2 = createActivityRecord(mDisplayContent); + activity2.setVisible(true); + activity2.mVisibleRequested = false; + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + closing.add(activity2); + + // Promote animation targets up to leaf Task level because there's only one TaskFragment in + // the Task. + assertEquals(new ArraySet<>(new WindowContainer[]{task1}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals(new ArraySet<>(new WindowContainer[]{activity2.getTask()}), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + } + + @Test + public void testGetAnimationTargets_closingTheOnlyTaskFragmentInTask() { + // [DefaultTDA] -+- [Task1] - [TaskFragment1] - [ActivityRecord1] (closing, visible) + // +- [Task2] - [ActivityRecord2] (opening, invisible) + final Task task1 = createTask(mDisplayContent); + final TaskFragment taskFragment1 = createTaskFragmentWithParentTask(task1, + false /* createEmbeddedTask */); + final ActivityRecord activity1 = taskFragment1.getTopMostActivity(); + activity1.setVisible(true); + activity1.mVisibleRequested = false; + + final ActivityRecord activity2 = createActivityRecord(mDisplayContent); + activity2.setVisible(false); + activity2.mVisibleRequested = true; + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity2); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + closing.add(activity1); + + // Promote animation targets up to leaf Task level because there's only one TaskFragment in + // the Task. + assertEquals(new ArraySet<>(new WindowContainer[]{activity2.getTask()}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals(new ArraySet<>(new WindowContainer[]{task1}), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + } + static class TestRemoteAnimationRunner implements IRemoteAnimationRunner { @Override public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, @@ -619,4 +777,254 @@ public class AppTransitionControllerTest extends WindowTestsBase { mAppTransitionController.getRemoteAnimationOverride( activity, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>())); } + + @Test + public void testOverrideTaskFragmentAdapter_overrideWithEmbeddedActivity() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( + new TestRemoteAnimationRunner(), 10, 1); + setupTaskFragmentRemoteAnimation(organizer, adapter); + + // Create a TaskFragment with embedded activity. + final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity( + createTask(mDisplayContent), organizer); + final ActivityRecord activity = taskFragment.getTopMostActivity(); + activity.allDrawn = true; + spyOn(mDisplayContent.mAppTransition); + + // Prepare a transition. + prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment); + + // Should be overridden. + verify(mDisplayContent.mAppTransition) + .overridePendingAppTransitionRemote(adapter, false /* sync */); + } + + @Test + public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( + new TestRemoteAnimationRunner(), 10, 1); + setupTaskFragmentRemoteAnimation(organizer, adapter); + + final Task task = createTask(mDisplayContent); + // Closing non-embedded activity. + final ActivityRecord closingActivity = createActivityRecord(task); + closingActivity.allDrawn = true; + // Opening TaskFragment with embedded activity. + final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer); + final ActivityRecord openingActivity = taskFragment.getTopMostActivity(); + openingActivity.allDrawn = true; + task.effectiveUid = openingActivity.getUid(); + spyOn(mDisplayContent.mAppTransition); + + // Prepare a transition. + prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment); + + // Should be overridden. + verify(mDisplayContent.mAppTransition) + .overridePendingAppTransitionRemote(adapter, false /* sync */); + } + + @Test + public void testOverrideTaskFragmentAdapter_overrideEmbeddedActivityWithDiffUid() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( + new TestRemoteAnimationRunner(), 10, 1); + setupTaskFragmentRemoteAnimation(organizer, adapter); + + final Task task = createTask(mDisplayContent); + // Closing TaskFragment with embedded activity. + final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer); + final ActivityRecord closingActivity = taskFragment1.getTopMostActivity(); + closingActivity.allDrawn = true; + closingActivity.info.applicationInfo.uid = 12345; + // Opening TaskFragment with embedded activity with different UID. + final TaskFragment taskFragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer); + final ActivityRecord openingActivity = taskFragment2.getTopMostActivity(); + openingActivity.info.applicationInfo.uid = 54321; + openingActivity.allDrawn = true; + spyOn(mDisplayContent.mAppTransition); + + // Prepare a transition. + prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1); + + // Should be overridden. + verify(mDisplayContent.mAppTransition) + .overridePendingAppTransitionRemote(adapter, false /* sync */); + } + + @Test + public void testOverrideTaskFragmentAdapter_noOverrideWithTwoApps() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( + new TestRemoteAnimationRunner(), 10, 1); + setupTaskFragmentRemoteAnimation(organizer, adapter); + + // Closing activity in Task1. + final ActivityRecord closingActivity = createActivityRecord(mDisplayContent); + closingActivity.allDrawn = true; + // Opening TaskFragment with embedded activity in Task2. + final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity( + createTask(mDisplayContent), organizer); + final ActivityRecord openingActivity = taskFragment.getTopMostActivity(); + openingActivity.allDrawn = true; + spyOn(mDisplayContent.mAppTransition); + + // Prepare a transition for TaskFragment. + prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment); + + // Should not be overridden. + verify(mDisplayContent.mAppTransition, never()) + .overridePendingAppTransitionRemote(adapter, false /* sync */); + } + + @Test + public void testOverrideTaskFragmentAdapter_noOverrideNonEmbeddedActivityWithDiffUid() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( + new TestRemoteAnimationRunner(), 10, 1); + setupTaskFragmentRemoteAnimation(organizer, adapter); + + final Task task = createTask(mDisplayContent); + // Closing TaskFragment with embedded activity. + final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer); + final ActivityRecord closingActivity = taskFragment.getTopMostActivity(); + closingActivity.allDrawn = true; + closingActivity.info.applicationInfo.uid = 12345; + task.effectiveUid = closingActivity.getUid(); + // Opening non-embedded activity with different UID. + final ActivityRecord openingActivity = createActivityRecord(task); + openingActivity.info.applicationInfo.uid = 54321; + openingActivity.allDrawn = true; + spyOn(mDisplayContent.mAppTransition); + + // Prepare a transition. + prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment); + + // Should not be overridden + verify(mDisplayContent.mAppTransition, never()) + .overridePendingAppTransitionRemote(adapter, false /* sync */); + } + + @Test + public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( + new TestRemoteAnimationRunner(), 10, 1); + setupTaskFragmentRemoteAnimation(organizer, adapter); + + // Create a TaskFragment with embedded activity. + final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity( + createTask(mDisplayContent), organizer); + final ActivityRecord activity = taskFragment.getTopMostActivity(); + activity.allDrawn = true; + // Set wallpaper as visible. + final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, + mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */); + spyOn(mDisplayContent.mWallpaperController); + doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible(); + spyOn(mDisplayContent.mAppTransition); + + // Prepare a transition. + prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment); + + // Should not be overridden when there is wallpaper in the transition. + verify(mDisplayContent.mAppTransition, never()) + .overridePendingAppTransitionRemote(adapter, false /* sync */); + } + + @Test + public void testTransitionGoodToGoForTaskFragments() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final Task task = createTask(mDisplayContent); + final TaskFragment changeTaskFragment = + createTaskFragmentWithEmbeddedActivity(task, organizer); + final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(organizer) + .build(); + changeTaskFragment.getTopMostActivity().allDrawn = true; + spyOn(mDisplayContent.mAppTransition); + spyOn(emptyTaskFragment); + + prepareAndTriggerAppTransition( + null /* openingActivity */, null /* closingActivity*/, changeTaskFragment); + + // Transition not ready because there is an empty non-finishing TaskFragment. + verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any()); + + doReturn(true).when(emptyTaskFragment).hasChild(); + emptyTaskFragment.remove(false /* withTransition */, "test"); + + mDisplayContent.mAppTransitionController.handleAppTransitionReady(); + + // Transition ready because the empty (no running activity) TaskFragment is requested to be + // removed. + verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any()); + } + + @Test + public void testTransitionGoodToGoForTaskFragments_detachedApp() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final Task task = createTask(mDisplayContent); + final TaskFragment changeTaskFragment = + createTaskFragmentWithEmbeddedActivity(task, organizer); + final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(organizer) + .build(); + changeTaskFragment.getTopMostActivity().allDrawn = true; + // To make sure that having a detached activity won't cause any issue. + final ActivityRecord detachedActivity = createActivityRecord(task); + detachedActivity.removeImmediately(); + assertNull(detachedActivity.getRootTask()); + spyOn(mDisplayContent.mAppTransition); + spyOn(emptyTaskFragment); + + prepareAndTriggerAppTransition( + null /* openingActivity */, detachedActivity, changeTaskFragment); + + // Transition not ready because there is an empty non-finishing TaskFragment. + verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any()); + + doReturn(true).when(emptyTaskFragment).hasChild(); + emptyTaskFragment.remove(false /* withTransition */, "test"); + + mDisplayContent.mAppTransitionController.handleAppTransitionReady(); + + // Transition ready because the empty (no running activity) TaskFragment is requested to be + // removed. + verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any()); + } + + /** Registers remote animation for the organizer. */ + private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer, + RemoteAnimationAdapter adapter) { + final ITaskFragmentOrganizer iOrganizer = + ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()); + final RemoteAnimationDefinition definition = new RemoteAnimationDefinition(); + definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter); + definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter); + definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter); + mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer); + mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition); + } + + private void prepareAndTriggerAppTransition(@Nullable ActivityRecord openingActivity, + @Nullable ActivityRecord closingActivity, @Nullable TaskFragment changingTaskFragment) { + if (openingActivity != null) { + mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0); + mDisplayContent.mOpeningApps.add(openingActivity); + } + if (closingActivity != null) { + mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CLOSE, 0); + mDisplayContent.mClosingApps.add(closingActivity); + } + if (changingTaskFragment != null) { + mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CHANGE, 0); + mDisplayContent.mChangingContainers.add(changingTaskFragment); + } + mDisplayContent.mAppTransitionController.handleAppTransitionReady(); + } }
\ No newline at end of file diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index c3279bf05737..2ea7fdaf6348 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -18,11 +18,19 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; +import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; +import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN; import static android.view.WindowManager.TRANSIT_OLD_UNSET; import static android.view.WindowManager.TRANSIT_OPEN; @@ -30,22 +38,32 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.WindowContainer.POSITION_TOP; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import android.graphics.Rect; +import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; import android.view.Display; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; import android.view.WindowManager; +import android.window.ITaskFragmentOrganizer; +import android.window.TaskFragmentOrganizer; import androidx.test.filters.SmallTest; @@ -77,13 +95,30 @@ public class AppTransitionTests extends WindowTestsBase { final ActivityRecord activity = createActivityRecord(dc); mDc.prepareAppTransition(TRANSIT_OPEN); + mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE); mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY); mDc.mOpeningApps.add(activity); assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY, AppTransitionController.getTransitCompatType(mDc.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null /* wallpaperTarget */, null /* oldWallpaper */, - false /*skipAppTransitionAnimation*/)); + mDisplayContent.mChangingContainers, null /* wallpaperTarget */, + null /* oldWallpaper */, false /*skipAppTransitionAnimation*/)); + } + + @Test + public void testKeyguardUnoccludeOcclude() { + final DisplayContent dc = createNewDisplay(Display.STATE_ON); + final ActivityRecord activity = createActivityRecord(dc); + + mDc.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE); + mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE); + mDc.mOpeningApps.add(activity); + assertEquals(TRANSIT_NONE, + AppTransitionController.getTransitCompatType(mDc.mAppTransition, + mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, + mDisplayContent.mChangingContainers, null /* wallpaperTarget */, + null /* oldWallpaper */, false /*skipAppTransitionAnimation*/)); + } @Test @@ -97,8 +132,8 @@ public class AppTransitionTests extends WindowTestsBase { assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY, AppTransitionController.getTransitCompatType(mDc.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null /* wallpaperTarget */, null /* oldWallpaper */, - false /*skipAppTransitionAnimation*/)); + mDisplayContent.mChangingContainers, null /* wallpaperTarget */, + null /* oldWallpaper */, false /*skipAppTransitionAnimation*/)); } @Test @@ -112,8 +147,8 @@ public class AppTransitionTests extends WindowTestsBase { assertEquals(TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE, AppTransitionController.getTransitCompatType(mDc.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null /* wallpaperTarget */, null /* oldWallpaper */, - false /*skipAppTransitionAnimation*/)); + mDisplayContent.mChangingContainers, null /* wallpaperTarget */, + null /* oldWallpaper */, false /*skipAppTransitionAnimation*/)); } @Test @@ -127,8 +162,8 @@ public class AppTransitionTests extends WindowTestsBase { assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY, AppTransitionController.getTransitCompatType(mDc.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null /* wallpaperTarget */, null /* oldWallpaper */, - false /*skipAppTransitionAnimation*/)); + mDisplayContent.mChangingContainers, null /* wallpaperTarget */, + null /* oldWallpaper */, false /*skipAppTransitionAnimation*/)); } @Test @@ -142,8 +177,129 @@ public class AppTransitionTests extends WindowTestsBase { assertEquals(TRANSIT_OLD_UNSET, AppTransitionController.getTransitCompatType(mDc.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null /* wallpaperTarget */, null /* oldWallpaper */, - true /*skipAppTransitionAnimation*/)); + mDisplayContent.mChangingContainers, null /* wallpaperTarget */, + null /* oldWallpaper */, true /*skipAppTransitionAnimation*/)); + } + + @Test + public void testTaskChangeWindowingMode() { + final ActivityRecord activity = createActivityRecord(mDc); + + mDc.prepareAppTransition(TRANSIT_OPEN); + mDc.prepareAppTransition(TRANSIT_CHANGE); + mDc.mOpeningApps.add(activity); // Make sure TRANSIT_CHANGE has the priority + mDc.mChangingContainers.add(activity.getTask()); + + assertEquals(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE, + AppTransitionController.getTransitCompatType(mDc.mAppTransition, + mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, + mDisplayContent.mChangingContainers, null /* wallpaperTarget */, + null /* oldWallpaper */, false /*skipAppTransitionAnimation*/)); + } + + @Test + public void testTaskFragmentChange() { + final ActivityRecord activity = createActivityRecord(mDc); + final TaskFragment taskFragment = new TaskFragment(mAtm, new Binder(), + true /* createdByOrganizer */, true /* isEmbedded */); + activity.getTask().addChild(taskFragment, POSITION_TOP); + activity.reparent(taskFragment, POSITION_TOP); + + mDc.prepareAppTransition(TRANSIT_OPEN); + mDc.prepareAppTransition(TRANSIT_CHANGE); + mDc.mOpeningApps.add(activity); // Make sure TRANSIT_CHANGE has the priority + mDc.mChangingContainers.add(taskFragment); + + assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, + AppTransitionController.getTransitCompatType(mDc.mAppTransition, + mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, + mDisplayContent.mChangingContainers, null /* wallpaperTarget */, + null /* oldWallpaper */, false /*skipAppTransitionAnimation*/)); + } + + @Test + public void testTaskFragmentOpeningTransition() { + final ActivityRecord activity = createHierarchyForTaskFragmentTest( + false /* createEmbeddedTask */); + activity.setVisible(false); + + mDisplayContent.prepareAppTransition(TRANSIT_OPEN); + mDisplayContent.mOpeningApps.add(activity); + assertEquals(TRANSIT_OLD_TASK_FRAGMENT_OPEN, + AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition, + mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, + mDisplayContent.mChangingContainers, null /* wallpaperTarget */, + null /* oldWallpaper */, false /* skipAppTransitionAnimation */)); + } + + @Test + public void testEmbeddedTaskOpeningTransition() { + final ActivityRecord activity = createHierarchyForTaskFragmentTest( + true /* createEmbeddedTask */); + activity.setVisible(false); + + mDisplayContent.prepareAppTransition(TRANSIT_OPEN); + mDisplayContent.mOpeningApps.add(activity); + assertEquals(TRANSIT_OLD_TASK_FRAGMENT_OPEN, + AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition, + mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, + mDisplayContent.mChangingContainers, null /* wallpaperTarget */, + null /* oldWallpaper */, false /* skipAppTransitionAnimation */)); + } + + @Test + public void testTaskFragmentClosingTransition() { + final ActivityRecord activity = createHierarchyForTaskFragmentTest( + false /* createEmbeddedTask */); + activity.setVisible(true); + + mDisplayContent.prepareAppTransition(TRANSIT_CLOSE); + mDisplayContent.mClosingApps.add(activity); + assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, + AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition, + mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, + mDisplayContent.mChangingContainers, null /* wallpaperTarget */, + null /* oldWallpaper */, false /* skipAppTransitionAnimation */)); + } + + @Test + public void testEmbeddedTaskClosingTransition() { + final ActivityRecord activity = createHierarchyForTaskFragmentTest( + true /* createEmbeddedTask */); + activity.setVisible(true); + + mDisplayContent.prepareAppTransition(TRANSIT_CLOSE); + mDisplayContent.mClosingApps.add(activity); + assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, + AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition, + mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, + mDisplayContent.mChangingContainers, null /* wallpaperTarget */, + null /* oldWallpaper */, false /* skipAppTransitionAnimation */)); + } + + /** + * Creates a {@link Task} with two {@link TaskFragment TaskFragments}. + * The bottom TaskFragment is to prevent + * {@link AppTransitionController#getAnimationTargets(ArraySet, ArraySet, boolean) the animation + * target} to promote to Task or above. + * + * @param createEmbeddedTask {@code true} to create embedded Task for verified TaskFragment + * @return The Activity to be put in either opening or closing Activity + */ + private ActivityRecord createHierarchyForTaskFragmentTest(boolean createEmbeddedTask) { + final Task parentTask = createTask(mDisplayContent); + final TaskFragment bottomTaskFragment = createTaskFragmentWithParentTask(parentTask, + false /* createEmbeddedTask */); + final ActivityRecord bottomActivity = bottomTaskFragment.getTopMostActivity(); + bottomActivity.setOccludesParent(true); + bottomActivity.setVisible(true); + + final TaskFragment verifiedTaskFragment = createTaskFragmentWithParentTask(parentTask, + createEmbeddedTask); + final ActivityRecord activity = verifiedTaskFragment.getTopMostActivity(); + activity.setOccludesParent(true); + + return activity; } @Test @@ -262,6 +418,43 @@ public class AppTransitionTests extends WindowTestsBase { mDc.mAppTransition.getAnimationStyleResId(attrs)); } + @Test + public void testActivityRecordReparentToTaskFragment() { + final ActivityRecord activity = createActivityRecord(mDc); + final SurfaceControl activityLeash = mock(SurfaceControl.class); + activity.setVisibility(true); + activity.setSurfaceControl(activityLeash); + final Task task = activity.getTask(); + + // Add a TaskFragment of half of the Task size. + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final ITaskFragmentOrganizer iOrganizer = + ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()); + mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(organizer) + .build(); + final Rect taskBounds = new Rect(); + task.getBounds(taskBounds); + taskFragment.setBounds(0, 0, taskBounds.right / 2, taskBounds.bottom); + spyOn(taskFragment); + mockSurfaceFreezerSnapshot(taskFragment.mSurfaceFreezer); + + assertTrue(mDc.mChangingContainers.isEmpty()); + assertFalse(mDc.mAppTransition.isTransitionSet()); + + // Schedule app transition when reparent activity to a TaskFragment of different size. + final Rect startBounds = new Rect(activity.getBounds()); + activity.reparent(taskFragment, POSITION_TOP); + + // It should transit at TaskFragment level with snapshot on the activity surface. + verify(taskFragment).initializeChangeTransition(activity.getBounds(), activityLeash); + assertTrue(mDc.mChangingContainers.contains(taskFragment)); + assertTrue(mDc.mAppTransition.containsTransitRequest(TRANSIT_CHANGE)); + assertEquals(startBounds, taskFragment.mSurfaceFreezer.mFreezeBounds); + } + private class TestRemoteAnimationRunner implements IRemoteAnimationRunner { boolean mCancelled = false; @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java index fd562c3f90b2..ebefeaff7f26 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java @@ -75,13 +75,19 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { @Test public void clipAfterAnim_boundsLayerZBoosted() { + final Task task = mActivity.getTask(); + final ActivityRecord topActivity = createActivityRecord(task); + task.assignChildLayers(mTransaction); + + assertThat(topActivity.getLastLayer()).isGreaterThan(mActivity.getLastLayer()); + mActivity.mNeedsAnimationBoundsLayer = true; mActivity.mNeedsZBoost = true; - mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, ANIMATION_TYPE_APP_TRANSITION); + verify(mTransaction).setLayer(eq(mActivity.mAnimationBoundsLayer), - intThat(layer -> layer >= ActivityRecord.Z_BOOST_BASE)); + intThat(layer -> layer > topActivity.getLastLayer())); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java index 31d46125fd70..af21e02ce27c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java @@ -35,10 +35,10 @@ import static android.window.DisplayAreaOrganizer.FEATURE_ROOT; import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST; import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION; +import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID; import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS; import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature; -import static com.android.server.wm.DisplayAreaPolicyBuilder.KEY_ROOT_DISPLAY_AREA_ID; import static com.google.common.truth.Truth.assertThat; diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java index 4e4e0edc694a..1f123ccef164 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java @@ -63,6 +63,7 @@ import android.platform.test.annotations.Presubmit; import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; +import android.window.DisplayAreaInfo; import android.window.IDisplayAreaOrganizer; import com.google.android.collect.Lists; @@ -570,6 +571,31 @@ public class DisplayAreaTest extends WindowTestsBase { } @Test + public void testGetDisplayAreaInfo() { + final DisplayArea<WindowContainer> displayArea = new DisplayArea<>( + mWm, BELOW_TASKS, "NewArea", FEATURE_VENDOR_FIRST); + mDisplayContent.addChild(displayArea, 0); + final DisplayAreaInfo info = displayArea.getDisplayAreaInfo(); + + assertThat(info.token).isEqualTo(displayArea.mRemoteToken.toWindowContainerToken()); + assertThat(info.configuration).isEqualTo(displayArea.getConfiguration()); + assertThat(info.displayId).isEqualTo(mDisplayContent.getDisplayId()); + assertThat(info.featureId).isEqualTo(displayArea.mFeatureId); + assertThat(info.rootDisplayAreaId).isEqualTo(mDisplayContent.mFeatureId); + + final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea(); + final int tdaIndex = tda.getParent().mChildren.indexOf(tda); + final RootDisplayArea root = + new DisplayAreaGroup(mWm, "TestRoot", FEATURE_VENDOR_FIRST + 1); + mDisplayContent.addChild(root, tdaIndex + 1); + displayArea.reparent(root, 0); + + final DisplayAreaInfo info2 = displayArea.getDisplayAreaInfo(); + + assertThat(info2.rootDisplayAreaId).isEqualTo(root.mFeatureId); + } + + @Test public void testRegisterSameFeatureOrganizer_expectThrowsException() { final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class); final IBinder binder = mock(IBinder.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 51e289f4d833..f3c1ec5b200e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -25,6 +25,7 @@ import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static android.view.Display.DEFAULT_DISPLAY; @@ -67,6 +68,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; import static com.android.dx.mockito.inline.extended.ExtendedMockito.same; @@ -107,9 +109,13 @@ import android.app.WindowConfiguration; import android.app.servertransaction.FixedRotationAdjustmentsItem; import android.content.res.Configuration; import android.graphics.Insets; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.hardware.display.VirtualDisplay; import android.metrics.LogMaker; +import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; @@ -122,6 +128,7 @@ import android.view.IDisplayWindowRotationController; import android.view.ISystemGestureExclusionListener; import android.view.IWindowManager; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceControl; @@ -134,6 +141,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; +import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.utils.WmDisplayCutout; @@ -141,6 +149,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; import java.util.ArrayList; import java.util.Arrays; @@ -592,7 +602,9 @@ public class DisplayContentTests extends WindowTestsBase { dc.setImeLayeringTarget(ws); // Adjust bounds so that matchesRootDisplayAreaBounds() returns false. - ws.mActivityRecord.getConfiguration().windowConfiguration.setBounds(new Rect(1, 1, 1, 1)); + final Rect bounds = new Rect(dc.getBounds()); + bounds.scale(0.5f); + ws.mActivityRecord.setBounds(bounds); assertFalse("matchesRootDisplayAreaBounds() should return false", ws.matchesDisplayAreaBounds()); @@ -870,19 +882,6 @@ public class DisplayContentTests extends WindowTestsBase { .setDisplayInfoOverrideFromWindowManager(dc.getDisplayId(), null); } - @UseTestDisplay - @Test - public void testClearLastFocusWhenReparentingFocusedWindow() { - final DisplayContent defaultDisplay = mWm.getDefaultDisplayContentLocked(); - final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, - defaultDisplay, "window"); - defaultDisplay.mLastFocus = window; - mDisplayContent.mCurrentFocus = window; - mDisplayContent.reParentWindowToken(window.mToken); - - assertNull(defaultDisplay.mLastFocus); - } - @Test public void testGetPreferredOptionsPanelGravityFromDifferentDisplays() { final DisplayContent portraitDisplay = createNewDisplay(); @@ -1217,10 +1216,10 @@ public class DisplayContentTests extends WindowTestsBase { win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; win.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION; win.getAttrs().insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; - final InsetsState requestedState = new InsetsState(); - requestedState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false); - requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false); - win.updateRequestedVisibility(requestedState); + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, false); + requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false); + win.setRequestedVisibilities(requestedVisibilities); win.mActivityRecord.mTargetSdk = P; performLayout(dc); @@ -1311,7 +1310,7 @@ public class DisplayContentTests extends WindowTestsBase { } @UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR, - W_NOTIFICATION_SHADE }) + W_INPUT_METHOD, W_NOTIFICATION_SHADE }) @Test public void testApplyTopFixedRotationTransform() { final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); @@ -1415,6 +1414,14 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals("The process should receive rotated configuration for compatibility", expectedProcConfig, app2.app.getConfiguration()); + // If the rotated activity requests to show IME, the IME window should use the + // transformation from activity to lay out in the same orientation. + mDisplayContent.setImeLayeringTarget(mAppWindow); + LocalServices.getService(WindowManagerInternal.class).onToggleImeRequested(true /* show */, + app.token, app.token, mDisplayContent.mDisplayId); + assertTrue(mImeWindow.mToken.hasFixedRotationTransform()); + assertTrue(mImeWindow.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM)); + // The fixed rotation transform can only be finished when all animation finished. doReturn(false).when(app2).isAnimating(anyInt(), anyInt()); mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app2.token); @@ -1530,7 +1537,7 @@ public class DisplayContentTests extends WindowTestsBase { unblockDisplayRotation(mDisplayContent); final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build(); app.setVisible(false); - app.setState(Task.ActivityState.RESUMED, "test"); + app.setState(ActivityRecord.State.RESUMED, "test"); mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN); mDisplayContent.mOpeningApps.add(app); final int newOrientation = getRotatedOrientation(mDisplayContent); @@ -1555,6 +1562,7 @@ public class DisplayContentTests extends WindowTestsBase { final ActivityRecord activity = createActivityRecord(mDisplayContent); final ActivityRecord recentsActivity = createActivityRecord(mDisplayContent); recentsActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); + doReturn(mock(RecentsAnimationController.class)).when(mWm).getRecentsAnimationController(); // Do not rotate if the recents animation is animating on top. mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity); @@ -1666,11 +1674,7 @@ public class DisplayContentTests extends WindowTestsBase { public void testShellTransitRotation() { DisplayContent dc = createNewDisplay(); - // Set-up mock shell transitions - final TestTransitionPlayer testPlayer = new TestTransitionPlayer( - mAtm.getTransitionController(), mAtm.mWindowOrganizerController); - mAtm.getTransitionController().registerTransitionPlayer(testPlayer); - + final TestTransitionPlayer testPlayer = registerTestTransitionPlayer(); final DisplayRotation dr = dc.getDisplayRotation(); doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean()); // Rotate 180 degree so the display doesn't have configuration change. This condition is @@ -2251,6 +2255,183 @@ public class DisplayContentTests extends WindowTestsBase { assertNotEquals(imeMenuDialog, mDisplayContent.findFocusedWindow()); } + @Test + public void testVirtualDisplayContent() { + MockitoSession mockSession = mockitoSession() + .initMocks(this) + .spyStatic(SurfaceControl.class) + .strictness(Strictness.LENIENT) + .startMocking(); + + // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to + // mirror. + final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken(); + + // GIVEN SurfaceControl can successfully mirror the provided surface. + Point surfaceSize = new Point( + mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(), + mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height()); + surfaceControlMirrors(surfaceSize); + + // WHEN creating the DisplayContent for a new virtual display. + final DisplayContent virtualDisplay = new TestDisplayContent.Builder(mAtm, + mDisplayInfo).build(); + + // THEN mirroring is initiated for the default display's DisplayArea. + assertThat(virtualDisplay.mTokenToMirror).isEqualTo(tokenToMirror); + + mockSession.finishMocking(); + } + + @Test + public void testVirtualDisplayContent_capturedAreaResized() { + MockitoSession mockSession = mockitoSession() + .initMocks(this) + .spyStatic(SurfaceControl.class) + .strictness(Strictness.LENIENT) + .startMocking(); + + // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to + // mirror. + final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken(); + + // GIVEN SurfaceControl can successfully mirror the provided surface. + Point surfaceSize = new Point( + mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(), + mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height()); + SurfaceControl mirroredSurface = surfaceControlMirrors(surfaceSize); + + // WHEN creating the DisplayContent for a new virtual display. + final DisplayContent virtualDisplay = new TestDisplayContent.Builder(mAtm, + mDisplayInfo).build(); + + // THEN mirroring is initiated for the default display's DisplayArea. + assertThat(virtualDisplay.mTokenToMirror).isEqualTo(tokenToMirror); + + float xScale = 0.7f; + float yScale = 2f; + Rect displayAreaBounds = new Rect(0, 0, Math.round(surfaceSize.x * xScale), + Math.round(surfaceSize.y * yScale)); + virtualDisplay.updateMirroredSurface(mTransaction, displayAreaBounds, surfaceSize); + + // THEN content in the captured DisplayArea is scaled to fit the surface size. + verify(mTransaction, atLeastOnce()).setMatrix(mirroredSurface, 1.0f / yScale, 0, 0, + 1.0f / yScale); + // THEN captured content is positioned in the centre of the output surface. + float scaledWidth = displayAreaBounds.width() / xScale; + float xInset = (surfaceSize.x - scaledWidth) / 2; + verify(mTransaction, atLeastOnce()).setPosition(mirroredSurface, xInset, 0); + + mockSession.finishMocking(); + } + + @Test + public void testVirtualDisplayContent_withoutSurface() { + MockitoSession mockSession = mockitoSession() + .initMocks(this) + .spyStatic(SurfaceControl.class) + .strictness(Strictness.LENIENT) + .startMocking(); + + // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to + // mirror. + setUpDefaultTaskDisplayAreaWindowToken(); + + // GIVEN SurfaceControl does not mirror a null surface. + Point surfaceSize = new Point( + mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(), + mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height()); + + // GIVEN a new VirtualDisplay with an associated surface. + final VirtualDisplay display = createVirtualDisplay(surfaceSize, null /* surface */); + final int displayId = display.getDisplay().getDisplayId(); + mWm.mRoot.onDisplayAdded(displayId); + + // WHEN getting the DisplayContent for the new virtual display. + DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId); + + // THEN mirroring is not started, since a null surface indicates the VirtualDisplay is off. + assertThat(actualDC.mTokenToMirror).isNull(); + + display.release(); + mockSession.finishMocking(); + } + + @Test + public void testVirtualDisplayContent_withSurface() { + MockitoSession mockSession = mockitoSession() + .initMocks(this) + .spyStatic(SurfaceControl.class) + .strictness(Strictness.LENIENT) + .startMocking(); + + // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to + // mirror. + final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken(); + + // GIVEN SurfaceControl can successfully mirror the provided surface. + Point surfaceSize = new Point( + mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(), + mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height()); + surfaceControlMirrors(surfaceSize); + + // GIVEN a new VirtualDisplay with an associated surface. + final VirtualDisplay display = createVirtualDisplay(surfaceSize, new Surface()); + final int displayId = display.getDisplay().getDisplayId(); + mWm.mRoot.onDisplayAdded(displayId); + + // WHEN getting the DisplayContent for the new virtual display. + DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId); + + // THEN mirroring is initiated for the default display's DisplayArea. + assertThat(actualDC.mTokenToMirror).isEqualTo(tokenToMirror); + + display.release(); + mockSession.finishMocking(); + } + + private class TestToken extends Binder { + } + + /** + * Creates a WindowToken associated with the default task DisplayArea, in order for that + * DisplayArea to be mirrored. + */ + private IBinder setUpDefaultTaskDisplayAreaWindowToken() { + // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to + // mirror. + final IBinder tokenToMirror = new TestToken(); + doReturn(tokenToMirror).when(mWm.mDisplayManagerInternal).getWindowTokenClientToMirror( + anyInt()); + + // GIVEN the default task display area is represented by the WindowToken. + spyOn(mWm.mWindowContextListenerController); + doReturn(mDefaultDisplay.getDefaultTaskDisplayArea()).when( + mWm.mWindowContextListenerController).getContainer(any()); + return tokenToMirror; + } + + /** + * SurfaceControl successfully creates a mirrored surface of the given size. + */ + private SurfaceControl surfaceControlMirrors(Point surfaceSize) { + // Do not set the parent, since the mirrored surface is the root of a new surface hierarchy. + SurfaceControl mirroredSurface = new SurfaceControl.Builder() + .setName("mirroredSurface") + .setBufferSize(surfaceSize.x, surfaceSize.y) + .setCallsite("mirrorSurface") + .build(); + doReturn(mirroredSurface).when(() -> SurfaceControl.mirrorSurface(any())); + doReturn(surfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize( + anyInt()); + return mirroredSurface; + } + + private VirtualDisplay createVirtualDisplay(Point size, Surface surface) { + return mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay", size.x, size.y, + DisplayMetrics.DENSITY_140, surface, VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR); + } + private void removeRootTaskTests(Runnable runnable) { final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, @@ -2261,10 +2442,10 @@ public class DisplayContentTests extends WindowTestsBase { ACTIVITY_TYPE_STANDARD, ON_TOP); final Task rootTask4 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); - final Task task1 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask1).build(); - final Task task2 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask2).build(); - final Task task3 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask3).build(); - final Task task4 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask4).build(); + final Task task1 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask1).build(); + final Task task2 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask2).build(); + final Task task3 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask3).build(); + final Task task4 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask4).build(); // Reordering root tasks while removing root tasks. doAnswer(invocation -> { @@ -2329,7 +2510,7 @@ public class DisplayContentTests extends WindowTestsBase { assertThat("topToBottom", actualWindows, is(reverseList(expectedWindowsBottomToTop))); } - private static int getRotatedOrientation(DisplayContent dc) { + static int getRotatedOrientation(DisplayContent dc) { return dc.mBaseDisplayWidth > dc.mBaseDisplayHeight ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE; diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java index 004e45a8be4b..b2b844b94d43 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java @@ -180,23 +180,23 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { } private int getNonDecorDisplayWidth(DisplayInfo di) { - return mDisplayPolicy.getNonDecorDisplayWidth(di.logicalWidth, di.logicalHeight, - di.rotation, 0 /* ui */, di.displayCutout); + return mDisplayPolicy.getNonDecorDisplaySize(di.logicalWidth, di.logicalHeight, + di.rotation, 0 /* ui */, di.displayCutout).x; } private int getNonDecorDisplayHeight(DisplayInfo di) { - return mDisplayPolicy.getNonDecorDisplayHeight(di.logicalWidth, di.logicalHeight, - di.rotation, 0 /* ui */, di.displayCutout); + return mDisplayPolicy.getNonDecorDisplaySize(di.logicalWidth, di.logicalHeight, + di.rotation, 0 /* ui */, di.displayCutout).y; } private int getConfigDisplayWidth(DisplayInfo di) { - return mDisplayPolicy.getConfigDisplayWidth(di.logicalWidth, di.logicalHeight, - di.rotation, 0 /* ui */, di.displayCutout); + return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight, + di.rotation, 0 /* ui */, di.displayCutout).x; } private int getConfigDisplayHeight(DisplayInfo di) { - return mDisplayPolicy.getConfigDisplayHeight(di.logicalWidth, di.logicalHeight, - di.rotation, 0 /* ui */, di.displayCutout); + return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight, + di.rotation, 0 /* ui */, di.displayCutout).y; } private static DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 03304bb9456a..4957ab96ace1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -24,7 +24,7 @@ import static android.view.InsetsState.ITYPE_TOP_GESTURES; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; @@ -50,13 +50,13 @@ import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.spy; import static org.testng.Assert.expectThrows; import android.graphics.Insets; -import android.graphics.PixelFormat; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.util.Pair; @@ -65,11 +65,11 @@ import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.Gravity; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.PrivacyIndicatorBounds; import android.view.RoundedCorners; import android.view.WindowInsets.Side; import android.view.WindowInsets.Type; -import android.view.WindowManager; import androidx.test.filters.SmallTest; @@ -109,18 +109,13 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow = spy(createWindow(null, TYPE_APPLICATION, "window")); // We only test window frames set by DisplayPolicy, so here prevents computeFrameLw from // changing those frames. - doNothing().when(mWindow).computeFrame(); - - final WindowManager.LayoutParams attrs = mWindow.mAttrs; - attrs.width = MATCH_PARENT; - attrs.height = MATCH_PARENT; - attrs.format = PixelFormat.TRANSLUCENT; + doNothing().when(mWindow).computeFrame(any()); spyOn(mStatusBarWindow); spyOn(mNavBarWindow); // Disabling this call for most tests since it can override the systemUiFlags when called. - doReturn(false).when(mDisplayPolicy).updateSystemUiVisibilityLw(); + doNothing().when(mDisplayPolicy).updateSystemBarAttributes(); updateDisplayFrames(); } @@ -219,7 +214,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - public void addingWindow_ignoresInsetsTypes_InWindowTypeWithPredefinedInsets() { + public void addingWindow_InWindowTypeWithPredefinedInsets() { mDisplayPolicy.removeWindowLw(mStatusBarWindow); // Removes the existing one. WindowState win = createWindow(null, TYPE_STATUS_BAR, "StatusBar"); win.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR}; @@ -230,7 +225,13 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { InsetsSourceProvider provider = mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR); - assertNotEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame()); + if (INSETS_LAYOUT_GENERALIZATION) { + // In the new flexible insets setup, the insets frame should always respect the window + // layout result. + assertEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame()); + } else { + assertNotEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame()); + } } @Test @@ -478,9 +479,9 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mDisplayContent.getInsetsStateController().getRawInsetsState() .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false); - final InsetsState requestedState = new InsetsState(); - requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false); - mWindow.updateRequestedVisibility(requestedState); + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false); + mWindow.setRequestedVisibilities(requestedVisibilities); addWindowWithRawInsetsState(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -498,9 +499,9 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mDisplayContent.getInsetsStateController().getRawInsetsState() .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false); - final InsetsState requestedState = new InsetsState(); - requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false); - mWindow.updateRequestedVisibility(requestedState); + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false); + mWindow.setRequestedVisibilities(requestedVisibilities); mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; addWindowWithRawInsetsState(mWindow); @@ -733,10 +734,12 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { @Test public void testFixedRotationInsetsSourceFrame() { + mDisplayContent.mBaseDisplayHeight = DISPLAY_HEIGHT; + mDisplayContent.mBaseDisplayWidth = DISPLAY_WIDTH; doReturn((mDisplayContent.getRotation() + 1) % 4).when(mDisplayContent) .rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord)); - mWindow.mAboveInsetsState.addSource(mDisplayContent.getInsetsStateController() - .getRawInsetsState().peekSource(ITYPE_STATUS_BAR)); + mWindow.mAboveInsetsState.set( + mDisplayContent.getInsetsStateController().getRawInsetsState()); final Rect frame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow) .getSource(ITYPE_STATUS_BAR).getFrame(); mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index b793be74c033..69700052ce74 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.RoundedCorners.NO_ROUNDED_CORNERS; @@ -37,7 +38,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM; -import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT; import static com.android.server.wm.utils.WmDisplayCutout.NO_CUTOUT; import static org.junit.Assert.assertEquals; @@ -107,8 +107,7 @@ public class DisplayPolicyTests extends WindowTestsBase { @Test public void testChooseNavigationColorWindowLw() { - final WindowState opaque = createOpaqueFullscreen(false); - + final WindowState candidate = createOpaqueFullscreen(false); final WindowState dimmingImTarget = createDimmingDialogWindow(true); final WindowState dimmingNonImTarget = createDimmingDialogWindow(false); @@ -116,45 +115,51 @@ public class DisplayPolicyTests extends WindowTestsBase { final WindowState invisibleIme = createInputMethodWindow(false, true, false); final WindowState imeNonDrawNavBar = createInputMethodWindow(true, false, false); - // If everything is null, return null + // If everything is null, return null. assertNull(null, DisplayPolicy.chooseNavigationColorWindowLw( - null, null, null, NAV_BAR_BOTTOM)); + null, null, NAV_BAR_BOTTOM)); - assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw( - opaque, opaque, null, NAV_BAR_BOTTOM)); + // If no IME windows, return candidate window. + assertEquals(candidate, DisplayPolicy.chooseNavigationColorWindowLw( + candidate, null, NAV_BAR_BOTTOM)); assertEquals(dimmingImTarget, DisplayPolicy.chooseNavigationColorWindowLw( - opaque, dimmingImTarget, null, NAV_BAR_BOTTOM)); + dimmingImTarget, null, NAV_BAR_BOTTOM)); assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw( - opaque, dimmingNonImTarget, null, NAV_BAR_BOTTOM)); + dimmingNonImTarget, null, NAV_BAR_BOTTOM)); - assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw( - null, null, visibleIme, NAV_BAR_BOTTOM)); - assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw( - null, dimmingImTarget, visibleIme, NAV_BAR_BOTTOM)); + // If IME is not visible, return candidate window. + assertEquals(null, DisplayPolicy.chooseNavigationColorWindowLw( + null, invisibleIme, NAV_BAR_BOTTOM)); + assertEquals(candidate, DisplayPolicy.chooseNavigationColorWindowLw( + candidate, invisibleIme, NAV_BAR_BOTTOM)); + assertEquals(dimmingImTarget, DisplayPolicy.chooseNavigationColorWindowLw( + dimmingImTarget, invisibleIme, NAV_BAR_BOTTOM)); assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw( - null, dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM)); + dimmingNonImTarget, invisibleIme, NAV_BAR_BOTTOM)); + + // If IME is visible, return candidate when the candidate window is not dimming. assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw( - opaque, opaque, visibleIme, NAV_BAR_BOTTOM)); + null, visibleIme, NAV_BAR_BOTTOM)); assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw( - opaque, dimmingImTarget, visibleIme, NAV_BAR_BOTTOM)); - assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw( - opaque, dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM)); + candidate, visibleIme, NAV_BAR_BOTTOM)); - assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw( - opaque, opaque, invisibleIme, NAV_BAR_BOTTOM)); - assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw( - opaque, opaque, invisibleIme, NAV_BAR_BOTTOM)); - assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw( - opaque, opaque, visibleIme, NAV_BAR_RIGHT)); + // If IME is visible and the candidate window is dimming, checks whether the dimming window + // can be IME tartget or not. + assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw( + dimmingImTarget, visibleIme, NAV_BAR_BOTTOM)); + assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw( + dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM)); // Only IME windows that have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS should be navigation color // window. - assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw( - opaque, opaque, imeNonDrawNavBar, NAV_BAR_BOTTOM)); + assertEquals(null, DisplayPolicy.chooseNavigationColorWindowLw( + null, imeNonDrawNavBar, NAV_BAR_BOTTOM)); + assertEquals(candidate, DisplayPolicy.chooseNavigationColorWindowLw( + candidate, imeNonDrawNavBar, NAV_BAR_BOTTOM)); assertEquals(dimmingImTarget, DisplayPolicy.chooseNavigationColorWindowLw( - opaque, dimmingImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM)); + dimmingImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM)); assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw( - opaque, dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM)); + dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM)); } @UseTestDisplay(addWindows = { W_NAVIGATION_BAR }) @@ -182,59 +187,27 @@ public class DisplayPolicyTests extends WindowTestsBase { // If there is no window, APPEARANCE_LIGHT_NAVIGATION_BARS is not allowed. assertEquals(0, - displayPolicy.updateLightNavigationBarLw( - APPEARANCE_LIGHT_NAVIGATION_BARS, null, null, - null, null)); - - // Opaque top fullscreen window overrides APPEARANCE_LIGHT_NAVIGATION_BARS flag. - assertEquals(0, displayPolicy.updateLightNavigationBarLw( - 0, opaqueDarkNavBar, opaqueDarkNavBar, null, opaqueDarkNavBar)); - assertEquals(0, displayPolicy.updateLightNavigationBarLw( - APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueDarkNavBar, opaqueDarkNavBar, null, - opaqueDarkNavBar)); - assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS, - displayPolicy.updateLightNavigationBarLw(0, opaqueLightNavBar, - opaqueLightNavBar, null, opaqueLightNavBar)); - assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS, - displayPolicy.updateLightNavigationBarLw(APPEARANCE_LIGHT_NAVIGATION_BARS, - opaqueLightNavBar, opaqueLightNavBar, null, opaqueLightNavBar)); - - // Dimming window clears APPEARANCE_LIGHT_NAVIGATION_BARS. - assertEquals(0, displayPolicy.updateLightNavigationBarLw( - 0, opaqueDarkNavBar, dimming, null, dimming)); - assertEquals(0, displayPolicy.updateLightNavigationBarLw( - 0, opaqueLightNavBar, dimming, null, dimming)); - assertEquals(0, displayPolicy.updateLightNavigationBarLw( - APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueDarkNavBar, dimming, null, dimming)); - assertEquals(0, displayPolicy.updateLightNavigationBarLw( - APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, dimming, null, dimming)); - assertEquals(0, displayPolicy.updateLightNavigationBarLw( - APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, dimming, imeDrawLightNavBar, - dimming)); - - // IME window clears APPEARANCE_LIGHT_NAVIGATION_BARS - assertEquals(0, displayPolicy.updateLightNavigationBarLw( - APPEARANCE_LIGHT_NAVIGATION_BARS, null, null, imeDrawDarkNavBar, - imeDrawDarkNavBar)); + displayPolicy.updateLightNavigationBarLw(APPEARANCE_LIGHT_NAVIGATION_BARS, null)); - // Even if the top fullscreen has APPEARANCE_LIGHT_NAVIGATION_BARS, IME window wins. + // Control window overrides APPEARANCE_LIGHT_NAVIGATION_BARS flag. + assertEquals(0, displayPolicy.updateLightNavigationBarLw(0, opaqueDarkNavBar)); assertEquals(0, displayPolicy.updateLightNavigationBarLw( - APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, opaqueLightNavBar, - imeDrawDarkNavBar, imeDrawDarkNavBar)); - - // IME window should be able to use APPEARANCE_LIGHT_NAVIGATION_BARS. - assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS, - displayPolicy.updateLightNavigationBarLw(0, opaqueDarkNavBar, - opaqueDarkNavBar, imeDrawLightNavBar, imeDrawLightNavBar)); + APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueDarkNavBar)); + assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS, displayPolicy.updateLightNavigationBarLw( + 0, opaqueLightNavBar)); + assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS, displayPolicy.updateLightNavigationBarLw( + APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar)); } - @UseTestDisplay(addWindows = W_ACTIVITY) + @UseTestDisplay(addWindows = {W_ACTIVITY, W_STATUS_BAR}) @Test public void testComputeTopFullscreenOpaqueWindow() { final WindowManager.LayoutParams attrs = mAppWindow.mAttrs; attrs.x = attrs.y = 0; attrs.height = attrs.width = WindowManager.LayoutParams.MATCH_PARENT; final DisplayPolicy policy = mDisplayContent.getDisplayPolicy(); + policy.addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs); + policy.applyPostLayoutPolicyLw( mAppWindow, attrs, null /* attached */, null /* imeTarget */); @@ -274,28 +247,38 @@ public class DisplayPolicyTests extends WindowTestsBase { @Test public void testOverlappingWithNavBar() { + final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR); + navSource.setFrame(new Rect(100, 200, 200, 300)); + testOverlappingWithNavBarType(navSource); + } + + @Test + public void testOverlappingWithExtraNavBar() { + final InsetsSource navSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR); + navSource.setFrame(new Rect(100, 200, 200, 300)); + testOverlappingWithNavBarType(navSource); + } + + private void testOverlappingWithNavBarType(InsetsSource navSource) { final WindowState targetWin = createApplicationWindow(); final WindowFrames winFrame = targetWin.getWindowFrames(); winFrame.mFrame.set(new Rect(100, 100, 200, 200)); - - final WindowState navigationBar = createNavigationBarWindow(); - - navigationBar.getFrame().set(new Rect(100, 200, 200, 300)); + targetWin.mAboveInsetsState.addSource(navSource); assertFalse("Freeform is overlapping with navigation bar", - DisplayPolicy.isOverlappingWithNavBar(targetWin, navigationBar)); + DisplayPolicy.isOverlappingWithNavBar(targetWin)); winFrame.mFrame.set(new Rect(100, 101, 200, 201)); assertTrue("Freeform should be overlapping with navigation bar (bottom)", - DisplayPolicy.isOverlappingWithNavBar(targetWin, navigationBar)); + DisplayPolicy.isOverlappingWithNavBar(targetWin)); winFrame.mFrame.set(new Rect(99, 200, 199, 300)); assertTrue("Freeform should be overlapping with navigation bar (right)", - DisplayPolicy.isOverlappingWithNavBar(targetWin, navigationBar)); + DisplayPolicy.isOverlappingWithNavBar(targetWin)); winFrame.mFrame.set(new Rect(199, 200, 299, 300)); assertTrue("Freeform should be overlapping with navigation bar (left)", - DisplayPolicy.isOverlappingWithNavBar(targetWin, navigationBar)); + DisplayPolicy.isOverlappingWithNavBar(targetWin)); } private WindowState createNavigationBarWindow() { @@ -313,7 +296,9 @@ public class DisplayPolicyTests extends WindowTestsBase { displayInfo.logicalHeight = 2000; displayInfo.rotation = ROTATION_0; - displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs); + WindowManager.LayoutParams attrs = mNavBarWindow.mAttrs; + displayPolicy.addWindowLw(mNavBarWindow, attrs); + mNavBarWindow.setRequestedSize(attrs.width, attrs.height); mNavBarWindow.getControllableInsetProvider().setServerVisible(true); final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState(); mImeWindow.mAboveInsetsState.set(state); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java index 683ed889d283..1d2baab934e5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java @@ -31,6 +31,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; import android.content.Context; import android.content.ContextWrapper; @@ -58,8 +59,6 @@ public class DisplayPolicyTestsBase extends WindowTestsBase { static final int DISPLAY_HEIGHT = 1000; static final int DISPLAY_DENSITY = 320; - static final int STATUS_BAR_HEIGHT = 10; - static final int NAV_BAR_HEIGHT = 15; static final int DISPLAY_CUTOUT_HEIGHT = 8; static final int IME_HEIGHT = 415; @@ -77,13 +76,12 @@ public class DisplayPolicyTestsBase extends WindowTestsBase { final TestContextWrapper context = new TestContextWrapper( mDisplayPolicy.getContext(), mDisplayPolicy.getCurrentUserResources()); final TestableResources resources = context.getResourceMocker(); - resources.addOverride(R.dimen.status_bar_height_portrait, STATUS_BAR_HEIGHT); - resources.addOverride(R.dimen.status_bar_height_landscape, STATUS_BAR_HEIGHT); resources.addOverride(R.dimen.navigation_bar_height, NAV_BAR_HEIGHT); resources.addOverride(R.dimen.navigation_bar_height_landscape, NAV_BAR_HEIGHT); resources.addOverride(R.dimen.navigation_bar_width, NAV_BAR_HEIGHT); resources.addOverride(R.dimen.navigation_bar_frame_height_landscape, NAV_BAR_HEIGHT); resources.addOverride(R.dimen.navigation_bar_frame_height, NAV_BAR_HEIGHT); + doReturn(STATUS_BAR_HEIGHT).when(mDisplayPolicy).getStatusBarHeightForRotation(anyInt()); doReturn(resources.getResources()).when(mDisplayPolicy).getCurrentUserResources(); doReturn(true).when(mDisplayPolicy).hasNavigationBar(); doReturn(true).when(mDisplayPolicy).hasStatusBar(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index 223dc3121cd6..32cca47b991c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -37,6 +37,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; @@ -44,6 +45,7 @@ import android.app.PendingIntent; import android.content.ClipData; import android.content.ClipDescription; import android.content.Intent; +import android.content.pm.ShortcutServiceInternal; import android.graphics.PixelFormat; import android.os.Binder; import android.os.IBinder; @@ -58,6 +60,7 @@ import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.View; import android.view.WindowManager; +import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; @@ -70,6 +73,8 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; @@ -87,6 +92,7 @@ import java.util.concurrent.TimeUnit; public class DragDropControllerTests extends WindowTestsBase { private static final int TIMEOUT_MS = 3000; private static final int TEST_UID = 12345; + private static final int TEST_PROFILE_UID = 12345 * UserHandle.PER_USER_RANGE; private static final int TEST_PID = 67890; private static final String TEST_PACKAGE = "com.test.package"; @@ -97,14 +103,20 @@ public class DragDropControllerTests extends WindowTestsBase { static class TestDragDropController extends DragDropController { private Runnable mCloseCallback; boolean mDeferDragStateClosed; + boolean mIsAccessibilityDrag; TestDragDropController(WindowManagerService service, Looper looper) { super(service, looper); } void setOnClosedCallbackLocked(Runnable runnable) { - assertTrue(dragDropActiveLocked()); - mCloseCallback = runnable; + if (mIsAccessibilityDrag) { + // Accessibility does not use animation + assertTrue(!dragDropActiveLocked()); + } else { + assertTrue(dragDropActiveLocked()); + mCloseCallback = runnable; + } } @Override @@ -171,6 +183,10 @@ public class DragDropControllerTests extends WindowTestsBase { } latch = new CountDownLatch(1); mTarget.setOnClosedCallbackLocked(latch::countDown); + if (mTarget.mIsAccessibilityDrag) { + mTarget.mIsAccessibilityDrag = false; + return; + } assertTrue(awaitInWmLock(() -> latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS))); } @@ -180,6 +196,12 @@ public class DragDropControllerTests extends WindowTestsBase { } @Test + public void testA11yDragFlow() { + mTarget.mIsAccessibilityDrag = true; + doA11yDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0); + } + + @Test public void testPerformDrag_NullDataWithGrantUri() { doDragAndDrop(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0); } @@ -369,6 +391,32 @@ public class DragDropControllerTests extends WindowTestsBase { } } + @Test + public void testValidateProfileAppShortcutArguments_notCallingUid() { + doReturn(PERMISSION_GRANTED).when(mWm.mContext) + .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS)); + final Session session = Mockito.spy(new Session(mWm, new IWindowSessionCallback.Stub() { + @Override + public void onAnimatorScaleChanged(float scale) {} + })); + final ShortcutServiceInternal shortcutService = mock(ShortcutServiceInternal.class); + final Intent[] shortcutIntents = new Intent[1]; + shortcutIntents[0] = new Intent(); + doReturn(shortcutIntents).when(shortcutService).createShortcutIntents(anyInt(), any(), + any(), any(), anyInt(), anyInt(), anyInt()); + LocalServices.removeServiceForTest(ShortcutServiceInternal.class); + LocalServices.addService(ShortcutServiceInternal.class, shortcutService); + + ArgumentCaptor<Integer> callingUser = ArgumentCaptor.forClass(Integer.class); + session.validateAndResolveDragMimeTypeExtras( + createClipDataForShortcut("test_package", "test_shortcut_id", + mock(UserHandle.class)), + TEST_PROFILE_UID, TEST_PID, TEST_PACKAGE); + verify(shortcutService).createShortcutIntents(callingUser.capture(), any(), + any(), any(), anyInt(), anyInt(), anyInt()); + assertTrue(callingUser.getValue() == UserHandle.getUserId(TEST_PROFILE_UID)); + } + private ClipData createClipDataForShortcut(String packageName, String shortcutId, UserHandle user) { final Intent data = new Intent(); @@ -436,4 +484,22 @@ public class DragDropControllerTests extends WindowTestsBase { appSession.kill(); } } + + private void doA11yDragAndDrop(int flags, ClipData data, float dropX, float dropY) { + spyOn(mTarget); + AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class); + when(accessibilityManager.isEnabled()).thenReturn(true); + doReturn(accessibilityManager).when(mTarget).getAccessibilityManager(); + startA11yDrag(flags, data, () -> { + boolean dropped = mTarget.dropForAccessibility(mWindow.mClient, dropX, dropY); + mToken = mWindow.mClient.asBinder(); + }); + } + + private void startA11yDrag(int flags, ClipData data, Runnable r) { + mToken = mTarget.performDrag(0, 0, mWindow.mClient, + flags | View.DRAG_FLAG_ACCESSIBILITY_ACTION, null, 0, 0, 0, 0, 0, data); + assertNotNull(mToken); + r.run(); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java index d7daa57cc9da..407f9cfdbe3e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.STATE_ON; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; @@ -70,7 +71,7 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { @Before public void setUp() throws Exception { - // Let the Display to be created with the DualDisplay policy. + // Let the Display be created with the DualDisplay policy. final DisplayAreaPolicy.Provider policyProvider = new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(); Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); @@ -78,6 +79,7 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { mController = new InputMethodMenuController(mock(InputMethodManagerService.class)); mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent .Builder(mAtm, 1000, 1000).build(); + mSecondaryDisplay.getDisplayInfo().state = STATE_ON; // Mock addWindowTokenWithOptions to create a test window token. mIWindowManager = WindowManagerGlobal.getWindowManagerService(); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index bf3ed692dc8e..ea5bf52af905 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; @@ -48,6 +49,7 @@ import android.platform.test.annotations.Presubmit; import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.InsetsVisibilities; import androidx.test.filters.SmallTest; @@ -80,7 +82,7 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test - public void testControlsForDispatch_dockedStackVisible() { + public void testControlsForDispatch_dockedTaskVisible() { addWindow(TYPE_STATUS_BAR, "statusBar"); addWindow(TYPE_NAVIGATION_BAR, "navBar"); @@ -93,25 +95,26 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test - public void testControlsForDispatch_freeformStackVisible() { + public void testControlsForDispatch_multiWindowTaskVisible() { addWindow(TYPE_STATUS_BAR, "statusBar"); addWindow(TYPE_NAVIGATION_BAR, "navBar"); - final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM, + final WindowState win = createWindow(null, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app"); final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win); - // The app must not control any bars. + // The app must not control any system bars. assertNull(controls); } @Test - public void testControlsForDispatch_dockedDividerControllerResizing() { + public void testControlsForDispatch_freeformTaskVisible() { addWindow(TYPE_STATUS_BAR, "statusBar"); addWindow(TYPE_NAVIGATION_BAR, "navBar"); - mDisplayContent.getDockedDividerController().setResizing(true); - final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch(); + final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM, + ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app"); + final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win); // The app must not control any system bars. assertNull(controls); @@ -179,9 +182,9 @@ public class InsetsPolicyTest extends WindowTestsBase { // Add a fullscreen (MATCH_PARENT x MATCH_PARENT) app window which hides status bar. final WindowState fullscreenApp = addWindow(TYPE_APPLICATION, "fullscreenApp"); - final InsetsState requestedState = new InsetsState(); - requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false); - fullscreenApp.updateRequestedVisibility(requestedState); + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false); + fullscreenApp.setRequestedVisibilities(requestedVisibilities); // Add a non-fullscreen dialog window. final WindowState dialog = addWindow(TYPE_APPLICATION, "dialog"); @@ -214,9 +217,9 @@ public class InsetsPolicyTest extends WindowTestsBase { // Assume mFocusedWindow is updated but mTopFullscreenOpaqueWindowState hasn't. final WindowState newFocusedFullscreenApp = addWindow(TYPE_APPLICATION, "newFullscreenApp"); - final InsetsState newRequestedState = new InsetsState(); - newRequestedState.getSource(ITYPE_STATUS_BAR).setVisible(true); - newFocusedFullscreenApp.updateRequestedVisibility(newRequestedState); + final InsetsVisibilities newRequestedVisibilities = new InsetsVisibilities(); + newRequestedVisibilities.setVisibility(ITYPE_STATUS_BAR, true); + newFocusedFullscreenApp.setRequestedVisibilities(newRequestedVisibilities); // Make sure status bar is hidden by previous insets state. mDisplayContent.getInsetsPolicy().updateBarControlTarget(fullscreenApp); @@ -277,10 +280,10 @@ public class InsetsPolicyTest extends WindowTestsBase { doNothing().when(policy).startAnimation(anyBoolean(), any()); // Make both system bars invisible. - final InsetsState requestedState = new InsetsState(); - requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false); - requestedState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false); - mAppWindow.updateRequestedVisibility(requestedState); + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false); + requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, false); + mAppWindow.setRequestedVisibilities(requestedVisibilities); policy.updateBarControlTarget(mAppWindow); waitUntilWindowAnimatorIdle(); assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState() @@ -288,7 +291,8 @@ public class InsetsPolicyTest extends WindowTestsBase { assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState() .getSource(ITYPE_NAVIGATION_BAR).isVisible()); - policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}); + policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}, + true /* isGestureOnSystemBar */); waitUntilWindowAnimatorIdle(); final InsetsSourceControl[] controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow); @@ -316,7 +320,8 @@ public class InsetsPolicyTest extends WindowTestsBase { final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(mAppWindow); - policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}); + policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}, + true /* isGestureOnSystemBar */); waitUntilWindowAnimatorIdle(); final InsetsSourceControl[] controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow); @@ -348,7 +353,8 @@ public class InsetsPolicyTest extends WindowTestsBase { final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(mAppWindow); - policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}); + policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}, + true /* isGestureOnSystemBar */); waitUntilWindowAnimatorIdle(); InsetsSourceControl[] controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow); @@ -371,7 +377,10 @@ public class InsetsPolicyTest extends WindowTestsBase { assertTrue(state.getSource(ITYPE_STATUS_BAR).isVisible()); assertTrue(state.getSource(ITYPE_NAVIGATION_BAR).isVisible()); - mAppWindow.updateRequestedVisibility(state); + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, true); + requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, true); + mAppWindow.setRequestedVisibilities(requestedVisibilities); policy.onInsetsModified(mAppWindow); waitUntilWindowAnimatorIdle(); @@ -396,7 +405,8 @@ public class InsetsPolicyTest extends WindowTestsBase { final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(app); - policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}); + policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}, + true /* isGestureOnSystemBar */); final InsetsSourceControl[] controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(app); policy.updateBarControlTarget(app2); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java index c483ae9fa4c5..2987f943f1c5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java @@ -31,7 +31,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.view.InsetsSource; -import android.view.InsetsState; +import android.view.InsetsVisibilities; import androidx.test.filters.SmallTest; @@ -203,9 +203,9 @@ public class InsetsSourceProviderTest extends WindowTestsBase { statusBar.getFrame().set(0, 0, 500, 100); mProvider.setWindow(statusBar, null, null); mProvider.updateControlForTarget(target, false /* force */); - InsetsState state = new InsetsState(); - state.getSource(ITYPE_STATUS_BAR).setVisible(false); - target.updateRequestedVisibility(state); + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false); + target.setRequestedVisibilities(requestedVisibilities); mProvider.updateClientVisibility(target); assertFalse(mSource.isVisible()); } @@ -216,9 +216,9 @@ public class InsetsSourceProviderTest extends WindowTestsBase { final WindowState target = createWindow(null, TYPE_APPLICATION, "target"); statusBar.getFrame().set(0, 0, 500, 100); mProvider.setWindow(statusBar, null, null); - InsetsState state = new InsetsState(); - state.getSource(ITYPE_STATUS_BAR).setVisible(false); - target.updateRequestedVisibility(state); + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false); + target.setRequestedVisibilities(requestedVisibilities); mProvider.updateClientVisibility(target); assertTrue(mSource.isVisible()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 80961d7afb70..94bc7f2dc0d1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -48,6 +48,7 @@ import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.InsetsVisibilities; import androidx.test.filters.SmallTest; @@ -174,10 +175,10 @@ public class InsetsStateControllerTest extends WindowTestsBase { mImeWindow.setHasSurface(true); getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); getController().onImeControlTargetChanged(mDisplayContent.getImeTarget(IME_TARGET_INPUT)); - final InsetsState requestedState = new InsetsState(); - requestedState.getSource(ITYPE_IME).setVisible(true); + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_IME, true); mDisplayContent.getImeTarget(IME_TARGET_INPUT).getWindow() - .updateRequestedVisibility(requestedState); + .setRequestedVisibilities(requestedVisibilities); getController().onInsetsModified(mDisplayContent.getImeTarget(IME_TARGET_INPUT)); // Send our spy window (app) into the system so that we can detect the invocation. @@ -331,7 +332,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { assertTrue(rotatedState.getSource(ITYPE_STATUS_BAR).isVisible()); provider.getSource().setVisible(false); - mDisplayContent.getInsetsPolicy().showTransient(new int[] { ITYPE_STATUS_BAR }); + mDisplayContent.getInsetsPolicy().showTransient(new int[] { ITYPE_STATUS_BAR }, + true /* isGestureOnSystemBar */); assertTrue(mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR)); assertFalse(app.getInsetsState().getSource(ITYPE_STATUS_BAR).isVisible()); diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index 7cb7c79d63a0..8a6db2c62a10 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -117,7 +117,7 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { Task rootTask = mTestDisplay.getDefaultTaskDisplayArea() .createRootTask(TEST_WINDOWING_MODE, ACTIVITY_TYPE_STANDARD, /* onTop */ true); mTestTask = new TaskBuilder(mSupervisor).setComponent(TEST_COMPONENT) - .setParentTask(rootTask).build(); + .setParentTaskFragment(rootTask).build(); mTestTask.mUserId = TEST_USER_ID; mTestTask.mLastNonFullscreenBounds = TEST_BOUNDS; mTestTask.setHasBeenVisible(true); @@ -353,7 +353,7 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { final Task anotherTaskOfTheSameUser = new TaskBuilder(mSupervisor) .setComponent(ALTERNATIVE_COMPONENT) .setUserId(TEST_USER_ID) - .setParentTask(stack) + .setParentTaskFragment(stack) .build(); anotherTaskOfTheSameUser.setWindowingMode(WINDOWING_MODE_FREEFORM); anotherTaskOfTheSameUser.setBounds(200, 300, 400, 500); @@ -365,7 +365,7 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { final Task anotherTaskOfDifferentUser = new TaskBuilder(mSupervisor) .setComponent(TEST_COMPONENT) .setUserId(ALTERNATIVE_USER_ID) - .setParentTask(stack) + .setParentTaskFragment(stack) .build(); anotherTaskOfDifferentUser.setWindowingMode(WINDOWING_MODE_FREEFORM); anotherTaskOfDifferentUser.setBounds(300, 400, 500, 600); diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java index 647a898a5361..1e86522a2307 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java @@ -62,7 +62,8 @@ public class LetterboxTest { mSurfaces = new SurfaceControlMocker(); mLetterbox = new Letterbox(mSurfaces, StubTransaction::new, () -> mAreCornersRounded, () -> Color.valueOf(mColor), - () -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha); + () -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha, + /* doubleTapCallback= */ x -> {}); mTransaction = spy(StubTransaction.class); } @@ -200,28 +201,37 @@ public class LetterboxTest { assertTrue(mLetterbox.needsApplySurfaceChanges()); mLetterbox.applySurfaceChanges(mTransaction); - verify(mTransaction).setAlpha(mSurfaces.top, mDarkScrimAlpha); + verify(mTransaction).setAlpha(mSurfaces.fullWindowSurface, mDarkScrimAlpha); } @Test - public void testApplySurfaceChanges_cornersNotRounded_surfaceBehindNotCreated() { + public void testApplySurfaceChanges_cornersNotRounded_surfaceFullWindowSurfaceNotCreated() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); mLetterbox.applySurfaceChanges(mTransaction); - assertNull(mSurfaces.behind); + assertNull(mSurfaces.fullWindowSurface); } @Test - public void testApplySurfaceChanges_cornersRounded_surfaceBehindCreated() { + public void testApplySurfaceChanges_cornersRounded_surfaceFullWindowSurfaceCreated() { mAreCornersRounded = true; mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); mLetterbox.applySurfaceChanges(mTransaction); - assertNotNull(mSurfaces.behind); + assertNotNull(mSurfaces.fullWindowSurface); } @Test - public void testNotIntersectsOrFullyContains_cornersRounded_doesNotCheckSurfaceBehind() { + public void testApplySurfaceChanges_wallpaperBackground_surfaceFullWindowSurfaceCreated() { + mHasWallpaperBackground = true; + mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); + mLetterbox.applySurfaceChanges(mTransaction); + + assertNotNull(mSurfaces.fullWindowSurface); + } + + @Test + public void testNotIntersectsOrFullyContains_cornersRounded() { mAreCornersRounded = true; mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0)); mLetterbox.applySurfaceChanges(mTransaction); @@ -249,8 +259,8 @@ public class LetterboxTest { public SurfaceControl right; private SurfaceControl.Builder mBottomBuilder; public SurfaceControl bottom; - private SurfaceControl.Builder mBehindBuilder; - public SurfaceControl behind; + private SurfaceControl.Builder mFullWindowSurfaceBuilder; + public SurfaceControl fullWindowSurface; @Override public SurfaceControl.Builder get() { @@ -265,8 +275,8 @@ public class LetterboxTest { mRightBuilder = (SurfaceControl.Builder) i.getMock(); } else if (((String) i.getArgument(0)).contains("bottom")) { mBottomBuilder = (SurfaceControl.Builder) i.getMock(); - } else if (((String) i.getArgument(0)).contains("behind")) { - mBehindBuilder = (SurfaceControl.Builder) i.getMock(); + } else if (((String) i.getArgument(0)).contains("fullWindow")) { + mFullWindowSurfaceBuilder = (SurfaceControl.Builder) i.getMock(); } return i.getMock(); }); @@ -281,8 +291,8 @@ public class LetterboxTest { right = control; } else if (i.getMock() == mBottomBuilder) { bottom = control; - } else if (i.getMock() == mBehindBuilder) { - behind = control; + } else if (i.getMock() == mFullWindowSurfaceBuilder) { + fullWindowSurface = control; } return control; }).when(builder).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 5af68021b201..284728397c9f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -331,7 +331,7 @@ public class RecentTasksTest extends WindowTestsBase { // other task Task task1 = createTaskBuilder(".Task1") .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) - .setParentTask(mTaskContainer.getRootHomeTask()).build(); + .setParentTaskFragment(mTaskContainer.getRootHomeTask()).build(); Task task2 = createTaskBuilder(".Task1") .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) .build(); @@ -471,8 +471,8 @@ public class RecentTasksTest extends WindowTestsBase { final Task root = createTaskBuilder(".CreatedByOrganizerRoot").build(); root.mCreatedByOrganizer = true; // Add organized and non-organized child. - final Task child1 = createTaskBuilder(".Task1").setParentTask(root).build(); - final Task child2 = createTaskBuilder(".Task2").setParentTask(root).build(); + final Task child1 = createTaskBuilder(".Task1").setParentTaskFragment(root).build(); + final Task child2 = createTaskBuilder(".Task2").setParentTaskFragment(root).build(); doReturn(true).when(child1).isOrganized(); doReturn(false).when(child2).isOrganized(); mRecentTasks.add(root); @@ -508,7 +508,8 @@ public class RecentTasksTest extends WindowTestsBase { // tasks because their intents are identical. mRecentTasks.add(task1); // Go home to trigger the removal of untracked tasks. - mRecentTasks.add(createTaskBuilder(".Home").setParentTask(mTaskContainer.getRootHomeTask()) + mRecentTasks.add(createTaskBuilder(".Home") + .setParentTaskFragment(mTaskContainer.getRootHomeTask()) .build()); triggerIdleToTrim(); @@ -675,7 +676,7 @@ public class RecentTasksTest extends WindowTestsBase { public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible() { // Create some set of tasks, some of which are visible and some are not Task homeTask = createTaskBuilder("com.android.pkg1", ".HomeTask") - .setParentTask(mTaskContainer.getRootHomeTask()) + .setParentTaskFragment(mTaskContainer.getRootHomeTask()) .build(); homeTask.mUserSetupComplete = true; mRecentTasks.add(homeTask); @@ -696,7 +697,7 @@ public class RecentTasksTest extends WindowTestsBase { t1.mUserSetupComplete = true; mRecentTasks.add(t1); Task homeTask = createTaskBuilder("com.android.pkg1", ".HomeTask") - .setParentTask(mTaskContainer.getRootHomeTask()) + .setParentTaskFragment(mTaskContainer.getRootHomeTask()) .build(); homeTask.mUserSetupComplete = true; mRecentTasks.add(homeTask); @@ -788,6 +789,19 @@ public class RecentTasksTest extends WindowTestsBase { } @Test + public void testVisibleEmbeddedTask_expectNotVisible() { + Task task = createTaskBuilder(".Task") + .setFlags(FLAG_ACTIVITY_NEW_TASK) + .build(); + doReturn(true).when(task).isEmbedded(); + mRecentTasks.add(task); + + assertThat(mCallbacksRecorder.mAdded).hasSize(1); + assertFalse("embedded task should not be visible recents", + mRecentTasks.isVisibleRecentTask(task)); + } + + @Test public void testFreezeTaskListOrder_reorderExistingTask() { // Add some tasks mRecentTasks.add(mTasks.get(0)); @@ -859,6 +873,40 @@ public class RecentTasksTest extends WindowTestsBase { } @Test + public void testFreezeTaskListOrder_replaceTask() { + // Create two tasks with the same affinity + Task affinityTask1 = createTaskBuilder(".AffinityTask1") + .setFlags(FLAG_ACTIVITY_NEW_TASK) + .build(); + Task affinityTask2 = createTaskBuilder(".AffinityTask2") + .setFlags(FLAG_ACTIVITY_NEW_TASK) + .build(); + affinityTask2.affinity = affinityTask1.affinity = "affinity"; + + // Add some tasks + mRecentTasks.add(mTasks.get(0)); + mRecentTasks.add(affinityTask1); + mRecentTasks.add(mTasks.get(1)); + mCallbacksRecorder.clear(); + + // Freeze the list + mRecentTasks.setFreezeTaskListReordering(); + assertTrue(mRecentTasks.isFreezeTaskListReorderingSet()); + + // Add the affinity task + mRecentTasks.add(affinityTask2); + + assertRecentTasksOrder(mTasks.get(1), + affinityTask2, + mTasks.get(0)); + + assertThat(mCallbacksRecorder.mAdded).hasSize(1); + assertThat(mCallbacksRecorder.mAdded).contains(affinityTask2); + assertThat(mCallbacksRecorder.mRemoved).hasSize(1); + assertThat(mCallbacksRecorder.mRemoved).contains(affinityTask1); + } + + @Test public void testFreezeTaskListOrder_timeout() { // Add some tasks mRecentTasks.add(mTasks.get(0)); @@ -902,10 +950,10 @@ public class RecentTasksTest extends WindowTestsBase { // Add a number of tasks (beyond the max) but ensure that nothing is trimmed because all // the tasks belong in stacks above the home stack - mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTask(homeStack).build()); - mRecentTasks.add(createTaskBuilder(".Task1").setParentTask(aboveHomeStack).build()); - mRecentTasks.add(createTaskBuilder(".Task2").setParentTask(aboveHomeStack).build()); - mRecentTasks.add(createTaskBuilder(".Task3").setParentTask(aboveHomeStack).build()); + mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTaskFragment(homeStack).build()); + mRecentTasks.add(createTaskBuilder(".Task1").setParentTaskFragment(aboveHomeStack).build()); + mRecentTasks.add(createTaskBuilder(".Task2").setParentTaskFragment(aboveHomeStack).build()); + mRecentTasks.add(createTaskBuilder(".Task3").setParentTaskFragment(aboveHomeStack).build()); triggerTrimAndAssertNoTasksTrimmed(); } @@ -923,11 +971,11 @@ public class RecentTasksTest extends WindowTestsBase { // Add a number of tasks (beyond the max) but ensure that only the task in the stack behind // the home stack is trimmed once a new task is added final Task behindHomeTask = createTaskBuilder(".Task1") - .setParentTask(behindHomeStack) + .setParentTaskFragment(behindHomeStack) .build(); mRecentTasks.add(behindHomeTask); - mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTask(homeStack).build()); - mRecentTasks.add(createTaskBuilder(".Task2").setParentTask(aboveHomeStack).build()); + mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTaskFragment(homeStack).build()); + mRecentTasks.add(createTaskBuilder(".Task2").setParentTaskFragment(aboveHomeStack).build()); triggerTrimAndAssertTrimmed(behindHomeTask); } @@ -943,10 +991,12 @@ public class RecentTasksTest extends WindowTestsBase { // Add a number of tasks (beyond the max) on each display, ensure that the tasks are not // removed - mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTask(homeTask).build()); - mRecentTasks.add(createTaskBuilder(".Task1").setParentTask(otherDisplayRootTask).build()); - mRecentTasks.add(createTaskBuilder(".Task2").setParentTask(otherDisplayRootTask).build()); - mRecentTasks.add(createTaskBuilder(".HomeTask2").setParentTask(homeTask).build()); + mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTaskFragment(homeTask).build()); + mRecentTasks.add(createTaskBuilder(".Task1").setParentTaskFragment(otherDisplayRootTask) + .build()); + mRecentTasks.add(createTaskBuilder(".Task2").setParentTaskFragment(otherDisplayRootTask) + .build()); + mRecentTasks.add(createTaskBuilder(".HomeTask2").setParentTaskFragment(homeTask).build()); triggerTrimAndAssertNoTasksTrimmed(); } @@ -976,7 +1026,7 @@ public class RecentTasksTest extends WindowTestsBase { Task t1 = createTaskBuilder("com.android.pkg1", ".Task1").build(); mRecentTasks.add(t1); mRecentTasks.add(createTaskBuilder("com.android.pkg1", ".HomeTask") - .setParentTask(mTaskContainer.getRootHomeTask()).build()); + .setParentTaskFragment(mTaskContainer.getRootHomeTask()).build()); Task t2 = createTaskBuilder("com.android.pkg2", ".Task2").build(); mRecentTasks.add(t2); mRecentTasks.add(createTaskBuilder("com.android.pkg1", ".PipTask") @@ -1194,7 +1244,8 @@ public class RecentTasksTest extends WindowTestsBase { } return new TaskSnapshot(1, new ComponentName("", ""), buffer, ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, - Surface.ROTATION_0, taskSize, new Rect() /* insets */, false /* isLowResolution */, + Surface.ROTATION_0, taskSize, new Rect() /* contentInsets */, + new Rect() /* letterboxInsets*/, false /* isLowResolution */, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* mSystemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */); } diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index d88fbee6ae13..b4c449a94a40 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -41,8 +41,8 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; @@ -124,7 +124,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { // Verify that the finish callback to reparent the leash is called verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), eq(adapter)); // Verify the animation canceled callback to the app was made - verify(mMockRunner).onAnimationCanceled(null /* taskSnapshot */); + verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */); verifyNoMoreInteractionsExceptAsBinder(mMockRunner); } @@ -207,7 +207,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { wallpaperWindowToken.cancelAnimation(); assertTrue(mController.isAnimatingTask(activity.getTask())); assertFalse(mController.isAnimatingWallpaper(wallpaperWindowToken)); - verify(mMockRunner, never()).onAnimationCanceled(null /* taskSnapshot */); + verify(mMockRunner, never()).onAnimationCanceled(null /* taskIds */, + null /* taskSnapshots */); } @Test @@ -254,10 +255,10 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { mController.setDeferredCancel(true /* deferred */, false /* screenshot */); mController.cancelAnimationWithScreenshot(false /* screenshot */); - verify(mMockRunner).onAnimationCanceled(null /* taskSnapshot */); + verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */); // Simulate the app transition finishing - mController.mAppTransitionListener.onAppTransitionStartingLocked(false, 0, 0, 0); + mController.mAppTransitionListener.onAppTransitionStartingLocked(false, false, 0, 0, 0); verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false); } @@ -281,7 +282,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */); mController.setDeferredCancel(true /* deferred */, true /* screenshot */); mController.cancelAnimationWithScreenshot(true /* screenshot */); - verify(mMockRunner).onAnimationCanceled(mMockTaskSnapshot /* taskSnapshot */); + verify(mMockRunner).onAnimationCanceled(any(int[].class) /* taskIds */, + any(TaskSnapshot[].class) /* taskSnapshots */); // Continue the animation (simulating a call to cleanupScreenshot()) mController.continueDeferredCancelAnimation(); @@ -322,7 +324,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(), anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */); mController.cancelAnimationWithScreenshot(true /* screenshot */); - verify(mMockRunner).onAnimationCanceled(any()); + verify(mMockRunner).onAnimationCanceled(any(), any()); // Simulate process crashing and ensure the animation is still canceled mController.binderDied(); @@ -439,6 +441,22 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { } @Test + public void testCheckRotationAfterCleanup() { + mWm.setRecentsAnimationController(mController); + spyOn(mDisplayContent.mFixedRotationTransitionListener); + doReturn(true).when(mDisplayContent.mFixedRotationTransitionListener) + .isTopFixedOrientationRecentsAnimating(); + // Rotation update is skipped while the recents animation is running. + assertFalse(mDisplayContent.getDisplayRotation().updateOrientation(DisplayContentTests + .getRotatedOrientation(mDefaultDisplay), false /* forceUpdate */)); + final int prevRotation = mDisplayContent.getRotation(); + mWm.cleanupRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); + waitHandlerIdle(mWm.mH); + // The display should be updated to the changed orientation after the animation is finished. + assertNotEquals(mDisplayContent.getRotation(), prevRotation); + } + + @Test public void testWallpaperHasFixedRotationApplied() { unblockDisplayRotation(mDefaultDisplay); mWm.setRecentsAnimationController(mController); @@ -684,7 +702,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { mController.setWillFinishToHome(true); mController.cancelAnimationForDisplayChange(); - verify(mMockRunner).onAnimationCanceled(any()); + verify(mMockRunner).onAnimationCanceled(any(), any()); verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_TOP, false); } @@ -699,7 +717,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { mController.setWillFinishToHome(false); mController.cancelAnimationForDisplayChange(); - verify(mMockRunner).onAnimationCanceled(any()); + verify(mMockRunner).onAnimationCanceled(any(), any()); verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_ORIGINAL_POSITION, false); } @@ -719,7 +737,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(), anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */); mController.cancelAnimationForHomeStart(); - verify(mMockRunner).onAnimationCanceled(any()); + verify(mMockRunner).onAnimationCanceled(any(), any()); // Continue the animation (simulating a call to cleanupScreenshot()) mController.continueDeferredCancelAnimation(); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index d017c19cac86..1b19a28a9790 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -31,8 +31,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.ActivityRecord.State.PAUSED; import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; -import static com.android.server.wm.Task.ActivityState.PAUSED; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.google.common.truth.Truth.assertThat; diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index b6cfa8e96a4c..575e0820eca9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -36,12 +36,14 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -51,6 +53,7 @@ import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.os.IInterface; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner; @@ -274,6 +277,32 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { } @Test + public void testOpeningTaskWithTopFinishingActivity() { + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "win"); + final Task task = win.getTask(); + final ActivityRecord topFinishing = new ActivityBuilder(mAtm).setTask(task).build(); + // Now the task contains: + // - Activity[1] (top, finishing, no window) + // - Activity[0] (has window) + topFinishing.finishing = true; + spyOn(mDisplayContent.mAppTransition); + doReturn(mController).when(mDisplayContent.mAppTransition).getRemoteAnimationController(); + task.applyAnimationUnchecked(null /* lp */, true /* enter */, TRANSIT_OLD_TASK_OPEN, + false /* isVoiceInteraction */, null /* sources */); + mController.goodToGo(TRANSIT_OLD_TASK_OPEN); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + try { + verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN), + appsCaptor.capture(), any(), any(), any()); + } catch (RemoteException ignored) { + } + assertEquals(1, appsCaptor.getValue().length); + assertEquals(RemoteAnimationTarget.MODE_OPENING, appsCaptor.getValue()[0].mode); + } + + @Test public void testChangeToSmallerSize() throws Exception { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); mDisplayContent.mChangingContainers.add(win.mActivityRecord); @@ -314,7 +343,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { verify(mMockTransaction).setWindowCrop( mMockLeash, app.startBounds.width(), app.startBounds.height()); verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0); - verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, -1, -1); + verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, app.startBounds.width(), + app.startBounds.height()); finishedCaptor.getValue().onAnimationFinished(); verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_WINDOW_ANIMATION), @@ -367,7 +397,63 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { verify(mMockTransaction).setWindowCrop( mMockLeash, app.startBounds.width(), app.startBounds.height()); verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0); - verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, -1, -1); + verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, app.startBounds.width(), + app.startBounds.height()); + + finishedCaptor.getValue().onAnimationFinished(); + verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_WINDOW_ANIMATION), + eq(record.mAdapter)); + verify(mThumbnailFinishedCallback).onAnimationFinished( + eq(ANIMATION_TYPE_WINDOW_ANIMATION), eq(record.mThumbnailAdapter)); + } finally { + mDisplayContent.mChangingContainers.clear(); + } + } + + @Test + public void testChangeToDifferentPosition() throws Exception { + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + mDisplayContent.mChangingContainers.add(win.mActivityRecord); + try { + final RemoteAnimationRecord record = mController.createRemoteAnimationRecord( + win.mActivityRecord, new Point(100, 100), null, new Rect(150, 150, 400, 400), + new Rect(50, 100, 150, 150)); + assertNotNull(record.mThumbnailAdapter); + ((AnimationAdapter) record.mAdapter) + .startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, + mFinishedCallback); + ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash, + mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback); + mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = + ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); + verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE), + appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(), + finishedCaptor.capture()); + assertEquals(1, appsCaptor.getValue().length); + final RemoteAnimationTarget app = appsCaptor.getValue()[0]; + assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode); + assertEquals(new Point(100, 100), app.position); + assertEquals(new Rect(150, 150, 400, 400), app.sourceContainerBounds); + assertEquals(new Rect(50, 100, 150, 150), app.startBounds); + assertEquals(mMockLeash, app.leash); + assertEquals(mMockThumbnailLeash, app.startLeash); + assertEquals(false, app.isTranslucent); + verify(mMockTransaction).setPosition( + mMockLeash, app.position.x + app.startBounds.left - app.screenSpaceBounds.left, + app.position.y + app.startBounds.top - app.screenSpaceBounds.top); + verify(mMockTransaction).setWindowCrop( + mMockLeash, app.startBounds.width(), app.startBounds.height()); + verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0); + verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, app.startBounds.width(), + app.startBounds.height()); finishedCaptor.getValue().onAnimationFinished(); verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_WINDOW_ANIMATION), @@ -615,6 +701,51 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { } } + @UseTestDisplay(addWindows = W_INPUT_METHOD) + @Test + public void testLaunchRemoteAnimationWithoutImeBehind() { + final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1"); + final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2"); + + // Simulating win1 has shown IME and being IME layering/input target + mDisplayContent.setImeLayeringTarget(win1); + mDisplayContent.setImeInputTarget(win1); + mImeWindow.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class); + mImeWindow.mWinAnimator.hide(mDisplayContent.getPendingTransaction(), "test"); + spyOn(mDisplayContent); + doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController).hasSurface(); + doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController) + .prepareToShowInTransaction(any(), anyFloat()); + makeWindowVisibleAndDrawn(mImeWindow); + assertTrue(mImeWindow.isOnScreen()); + assertFalse(mImeWindow.isParentWindowHidden()); + + try { + // Simulating now win1 is being covered by the lockscreen which has no surface, + // and then launching an activity win2 with the remote animation + win1.mHasSurface = false; + mDisplayContent.mOpeningApps.add(win2.mActivityRecord); + final AnimationAdapter adapter = mController.createRemoteAnimationRecord( + win2.mActivityRecord, new Point(50, 100), null, + new Rect(50, 100, 150, 150), null).mAdapter; + adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, + mFinishedCallback); + + mDisplayContent.applySurfaceChangesTransaction(); + mController.goodToGo(TRANSIT_OLD_TASK_OPEN); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + + verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN), + any(), any(), any(), any()); + // Verify the IME window won't apply surface change transaction with forAllImeWindows + verify(mDisplayContent, never()).forAllImeWindows(any(), eq(true)); + } catch (Exception e) { + // no-op + } finally { + mDisplayContent.mOpeningApps.clear(); + } + } + private AnimationAdapter setupForNonAppTargetNavBar(int transit, boolean shouldAttachNavBar) { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); mDisplayContent.mOpeningApps.add(win.mActivityRecord); diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java index 0c6545c2438f..030733bf4424 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java @@ -30,28 +30,28 @@ import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.ActivityRecord.State.DESTROYED; +import static com.android.server.wm.ActivityRecord.State.DESTROYING; +import static com.android.server.wm.ActivityRecord.State.FINISHING; +import static com.android.server.wm.ActivityRecord.State.INITIALIZING; +import static com.android.server.wm.ActivityRecord.State.PAUSING; +import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.ActivityRecord.State.STOPPED; +import static com.android.server.wm.ActivityRecord.State.STOPPING; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE; -import static com.android.server.wm.Task.ActivityState.DESTROYED; -import static com.android.server.wm.Task.ActivityState.DESTROYING; -import static com.android.server.wm.Task.ActivityState.FINISHING; -import static com.android.server.wm.Task.ActivityState.PAUSING; -import static com.android.server.wm.Task.ActivityState.RESUMED; -import static com.android.server.wm.Task.ActivityState.STOPPED; -import static com.android.server.wm.Task.ActivityState.STOPPING; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT; import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT; -import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE; -import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE; -import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; import static com.android.server.wm.TaskDisplayArea.getRootTaskAbove; +import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE; +import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE; +import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; @@ -89,9 +89,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; -import java.util.function.Consumer; - /** * Tests for the root {@link Task} behavior. * @@ -194,7 +191,6 @@ public class RootTaskTests extends WindowTestsBase { final Task task = createTaskInRootTask(rootTask, 0 /* userId */); // Root task removal is deferred if one of its child is animating. - doReturn(true).when(rootTask).hasWindowsAlive(); doReturn(rootTask).when(task).getAnimatingContainer( eq(TRANSITION | CHILDREN), anyInt()); @@ -280,11 +276,11 @@ public class RootTaskTests extends WindowTestsBase { public void testResumedActivity() { final ActivityRecord r = new ActivityBuilder(mAtm).setCreateTask(true).build(); final Task task = r.getTask(); - assertNull(task.getResumedActivity()); + assertNull(task.getTopResumedActivity()); r.setState(RESUMED, "testResumedActivity"); - assertEquals(r, task.getResumedActivity()); + assertEquals(r, task.getTopResumedActivity()); r.setState(PAUSING, "testResumedActivity"); - assertNull(task.getResumedActivity()); + assertNull(task.getTopResumedActivity()); } @Test @@ -295,15 +291,15 @@ public class RootTaskTests extends WindowTestsBase { final Task task = r.getTask(); // Ensure moving task between two root tasks updates resumed activity r.setState(RESUMED, "testResumedActivityFromTaskReparenting"); - assertEquals(r, rootTask.getResumedActivity()); + assertEquals(r, rootTask.getTopResumedActivity()); final Task destRootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); task.reparent(destRootTask, true /* toTop */, REPARENT_KEEP_ROOT_TASK_AT_FRONT, false /* animate */, true /* deferResume*/, "testResumedActivityFromTaskReparenting"); - assertNull(rootTask.getResumedActivity()); - assertEquals(r, destRootTask.getResumedActivity()); + assertNull(rootTask.getTopResumedActivity()); + assertEquals(r, destRootTask.getTopResumedActivity()); } @Test @@ -314,15 +310,15 @@ public class RootTaskTests extends WindowTestsBase { final Task task = r.getTask(); // Ensure moving task between two root tasks updates resumed activity r.setState(RESUMED, "testResumedActivityFromActivityReparenting"); - assertEquals(r, rootTask.getResumedActivity()); + assertEquals(r, rootTask.getTopResumedActivity()); final Task destRootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); task.reparent(destRootTask, true /*toTop*/, REPARENT_MOVE_ROOT_TASK_TO_FRONT, false /* animate */, false /* deferResume*/, "testResumedActivityFromActivityReparenting"); - assertNull(rootTask.getResumedActivity()); - assertEquals(r, destRootTask.getResumedActivity()); + assertNull(rootTask.getTopResumedActivity()); + assertEquals(r, destRootTask.getTopResumedActivity()); } @Test @@ -333,7 +329,7 @@ public class RootTaskTests extends WindowTestsBase { // Create primary splitscreen root task. final Task primarySplitScreen = new TaskBuilder(mAtm.mTaskSupervisor) - .setParentTask(organizer.mPrimary) + .setParentTaskFragment(organizer.mPrimary) .setOnTop(true) .build(); @@ -509,8 +505,8 @@ public class RootTaskTests extends WindowTestsBase { targetActivity); final ComponentName alias = new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME, aliasActivity); - final Task parentTask = new TaskBuilder(mAtm.mTaskSupervisor).build(); - final Task task = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(parentTask).build(); + final Task parentTask = new TaskBuilder(mSupervisor).build(); + final Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(parentTask).build(); task.origActivity = alias; task.realActivity = target; new ActivityBuilder(mAtm).setComponent(target).setTask(task).setTargetActivity( @@ -618,9 +614,9 @@ public class RootTaskTests extends WindowTestsBase { doReturn(false).when(splitScreenSecondary2).isTranslucent(any()); assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); - assertEquals(TASK_VISIBILITY_INVISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, splitScreenSecondary.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, splitScreenSecondary2.getVisibility(null /* starting */)); // First split-screen secondary should be visible behind another translucent split-screen @@ -628,9 +624,9 @@ public class RootTaskTests extends WindowTestsBase { doReturn(true).when(splitScreenSecondary2).isTranslucent(any()); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, splitScreenSecondary.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, splitScreenSecondary2.getVisibility(null /* starting */)); final Task assistantRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, @@ -642,13 +638,13 @@ public class RootTaskTests extends WindowTestsBase { assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, assistantRootTask.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_INVISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, splitScreenPrimary.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_INVISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, splitScreenSecondary.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_INVISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, splitScreenSecondary2.getVisibility(null /* starting */)); // Split-screen root tasks should be visible behind a translucent fullscreen root task. @@ -657,13 +653,13 @@ public class RootTaskTests extends WindowTestsBase { assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, assistantRootTask.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, splitScreenPrimary.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, splitScreenSecondary.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, splitScreenSecondary2.getVisibility(null /* starting */)); // Assistant root task shouldn't be visible behind translucent split-screen root task, @@ -678,25 +674,25 @@ public class RootTaskTests extends WindowTestsBase { assertTrue(assistantRootTask.shouldBeVisible(null /* starting */)); assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, assistantRootTask.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_INVISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, splitScreenPrimary.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_INVISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, splitScreenSecondary.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_INVISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, splitScreenSecondary2.getVisibility(null /* starting */)); } else { assertFalse(assistantRootTask.shouldBeVisible(null /* starting */)); assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); - assertEquals(TASK_VISIBILITY_INVISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, assistantRootTask.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, splitScreenPrimary.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_INVISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, splitScreenSecondary.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, splitScreenSecondary2.getVisibility(null /* starting */)); } } @@ -707,45 +703,51 @@ public class RootTaskTests extends WindowTestsBase { WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, true /* onTop */); final Task splitPrimary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */); + // Creating as two-level tasks so home task can be reparented to split-secondary root task. final Task splitSecondary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */); + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */, + true /* twoLevelTask */); doReturn(false).when(homeRootTask).isTranslucent(any()); doReturn(false).when(splitPrimary).isTranslucent(any()); doReturn(false).when(splitSecondary).isTranslucent(any()); - // Re-parent home to split secondary. homeRootTask.reparent(splitSecondary, POSITION_TOP); // Current tasks should be visible. - assertEquals(TASK_VISIBILITY_VISIBLE, splitPrimary.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, splitSecondary.getVisibility(null /* starting */)); + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, + splitPrimary.getVisibility(null /* starting */)); + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, + splitSecondary.getVisibility(null /* starting */)); // Home task should still be visible even though it is a child of another visible task. - assertEquals(TASK_VISIBILITY_VISIBLE, homeRootTask.getVisibility(null /* starting */)); + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, + homeRootTask.getVisibility(null /* starting */)); // Add fullscreen translucent task that partially occludes split tasks final Task translucentRootTask = createStandardRootTaskForVisibilityTest( WINDOWING_MODE_FULLSCREEN, true /* translucent */); // Fullscreen translucent task should be visible - assertEquals(TASK_VISIBILITY_VISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, translucentRootTask.getVisibility(null /* starting */)); // Split tasks should be visible behind translucent - assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, splitPrimary.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, splitSecondary.getVisibility(null /* starting */)); // Home task should be visible behind translucent since its parent is visible behind // translucent. - assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, homeRootTask.getVisibility(null /* starting */)); // Hide split-secondary splitSecondary.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, true /* set */); // Home split secondary and home task should be invisible. - assertEquals(TASK_VISIBILITY_INVISIBLE, splitSecondary.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_INVISIBLE, homeRootTask.getVisibility(null /* starting */)); + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, + splitSecondary.getVisibility(null /* starting */)); + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, + homeRootTask.getVisibility(null /* starting */)); } @Test @@ -757,9 +759,9 @@ public class RootTaskTests extends WindowTestsBase { createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); - assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, bottomRootTask.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, translucentRootTask.getVisibility(null /* starting */)); } @@ -775,10 +777,12 @@ public class RootTaskTests extends WindowTestsBase { createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); - assertEquals(TASK_VISIBILITY_INVISIBLE, bottomRootTask.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_INVISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, + bottomRootTask.getVisibility(null /* starting */)); + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, translucentRootTask.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, opaqueRootTask.getVisibility(null /* starting */)); + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, + opaqueRootTask.getVisibility(null /* starting */)); } @Test @@ -793,10 +797,11 @@ public class RootTaskTests extends WindowTestsBase { createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); - assertEquals(TASK_VISIBILITY_INVISIBLE, bottomRootTask.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, + bottomRootTask.getVisibility(null /* starting */)); + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, opaqueRootTask.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, translucentRootTask.getVisibility(null /* starting */)); } @@ -809,9 +814,9 @@ public class RootTaskTests extends WindowTestsBase { createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); - assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, bottomTranslucentRootTask.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, translucentRootTask.getVisibility(null /* starting */)); } @@ -824,9 +829,10 @@ public class RootTaskTests extends WindowTestsBase { createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); - assertEquals(TASK_VISIBILITY_INVISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, bottomTranslucentRootTask.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, opaqueRootTask.getVisibility(null /* starting */)); + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, + opaqueRootTask.getVisibility(null /* starting */)); } @Test @@ -840,16 +846,17 @@ public class RootTaskTests extends WindowTestsBase { final Task pinnedRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, bottomRootTask.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, translucentRootTask.getVisibility(null /* starting */)); // Add an activity to the pinned root task so it isn't considered empty for visibility // check. final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm) .setTask(pinnedRootTask) .build(); - assertEquals(TASK_VISIBILITY_VISIBLE, pinnedRootTask.getVisibility(null /* starting */)); + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, + pinnedRootTask.getVisibility(null /* starting */)); } @Test @@ -1142,12 +1149,12 @@ public class RootTaskTests extends WindowTestsBase { } else if (twoLevelTask) { task = new TaskBuilder(mSupervisor) .setTaskDisplayArea(taskDisplayArea) - .setWindowingMode(windowingMode) .setActivityType(activityType) .setOnTop(onTop) .setCreateActivity(true) .setCreateParentTask(true) .build().getRootTask(); + task.setWindowingMode(windowingMode); } else { task = new TaskBuilder(mSupervisor) .setTaskDisplayArea(taskDisplayArea) @@ -1301,9 +1308,9 @@ public class RootTaskTests extends WindowTestsBase { final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build(); topActivity.info.flags |= FLAG_RESUME_WHILE_PAUSING; - task.startPausingLocked(false /* uiSleeping */, topActivity, + task.startPausing(false /* uiSleeping */, topActivity, "test"); - verify(task).completePauseLocked(anyBoolean(), eq(topActivity)); + verify(task).completePause(anyBoolean(), eq(topActivity)); } @Test @@ -1494,41 +1501,6 @@ public class RootTaskTests extends WindowTestsBase { } @Test - public void testIterateOccludedActivity() { - final ArrayList<ActivityRecord> occludedActivities = new ArrayList<>(); - final Consumer<ActivityRecord> handleOccludedActivity = occludedActivities::add; - final Task task = new TaskBuilder(mSupervisor).build(); - final ActivityRecord bottomActivity = new ActivityBuilder(mAtm).setTask(task).build(); - final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build(); - // Top activity occludes bottom activity. - doReturn(true).when(task).shouldBeVisible(any()); - assertTrue(topActivity.shouldBeVisible()); - assertFalse(bottomActivity.shouldBeVisible()); - - task.forAllOccludedActivities(handleOccludedActivity); - assertThat(occludedActivities).containsExactly(bottomActivity); - - // Top activity doesn't occlude parent, so the bottom activity is not occluded. - doReturn(false).when(topActivity).occludesParent(); - assertTrue(bottomActivity.shouldBeVisible()); - - occludedActivities.clear(); - task.forAllOccludedActivities(handleOccludedActivity); - assertThat(occludedActivities).isEmpty(); - - // A finishing activity should not occlude other activities behind. - final ActivityRecord finishingActivity = new ActivityBuilder(mAtm).setTask(task).build(); - finishingActivity.finishing = true; - doCallRealMethod().when(finishingActivity).occludesParent(); - assertTrue(topActivity.shouldBeVisible()); - assertTrue(bottomActivity.shouldBeVisible()); - - occludedActivities.clear(); - task.forAllOccludedActivities(handleOccludedActivity); - assertThat(occludedActivities).isEmpty(); - } - - @Test public void testClearUnknownAppVisibilityBehindFullscreenActivity() { final UnknownAppVisibilityController unknownAppVisibilityController = mDefaultTaskDisplayArea.mDisplayContent.mUnknownAppVisibilityController; @@ -1544,7 +1516,7 @@ public class RootTaskTests extends WindowTestsBase { activities[i] = r; doReturn(null).when(mAtm).getProcessController( eq(r.processName), eq(r.info.applicationInfo.uid)); - r.setState(Task.ActivityState.INITIALIZING, "test"); + r.setState(INITIALIZING, "test"); // Ensure precondition that the activity is opaque. assertTrue(r.occludesParent()); mSupervisor.startSpecificActivity(r, false /* andResume */, @@ -1552,14 +1524,13 @@ public class RootTaskTests extends WindowTestsBase { } mSupervisor.endDeferResume(); - setBooted(mAtm); // 2 activities are started while keyguard is locked, so they are waiting to be resolved. assertFalse(unknownAppVisibilityController.allResolved()); - // Assume the top activity is going to resume and - // {@link RootWindowContainer#cancelInitializingActivities} should clear the unknown - // visibility records that are occluded. - task.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */); + // Any common path that updates activity visibility should clear the unknown visibility + // records that are no longer visible according to hierarchy. + task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, + false /* preserveWindows */); // Assume the top activity relayouted, just remove it directly. unknownAppVisibilityController.appRemovedOrHidden(activities[1]); // All unresolved records should be removed. diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index 9cf29d4dcc50..4069f0f41d90 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -31,13 +31,14 @@ import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.wm.ActivityRecord.State.FINISHING; +import static com.android.server.wm.ActivityRecord.State.PAUSED; +import static com.android.server.wm.ActivityRecord.State.PAUSING; +import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.ActivityRecord.State.STOPPED; +import static com.android.server.wm.ActivityRecord.State.STOPPING; import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE; -import static com.android.server.wm.Task.ActivityState.FINISHING; -import static com.android.server.wm.Task.ActivityState.PAUSED; -import static com.android.server.wm.Task.ActivityState.PAUSING; -import static com.android.server.wm.Task.ActivityState.STOPPED; -import static com.android.server.wm.Task.ActivityState.STOPPING; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.google.common.truth.Truth.assertThat; @@ -167,7 +168,7 @@ public class RootWindowContainerTests extends WindowTestsBase { @Test public void testTaskLayerRank() { final Task rootTask = new TaskBuilder(mSupervisor).build(); - final Task task1 = new TaskBuilder(mSupervisor).setParentTask(rootTask).build(); + final Task task1 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build(); new ActivityBuilder(mAtm).setTask(task1).build().mVisibleRequested = true; mWm.mRoot.rankTaskLayers(); @@ -365,7 +366,7 @@ public class RootWindowContainerTests extends WindowTestsBase { TaskDisplayArea defaultTaskDisplayArea = display.getDefaultTaskDisplayArea(); doReturn(isFocusedTask ? task : null).when(defaultTaskDisplayArea).getFocusedRootTask(); mRootWindowContainer.applySleepTokens(true); - verify(task, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked(); + verify(task, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleeping(); verify(task, times(expectResumeTopActivity ? 1 : 0)).resumeTopActivityUncheckedLocked( null /* target */, null /* targetOptions */); } @@ -386,7 +387,7 @@ public class RootWindowContainerTests extends WindowTestsBase { // landscape and the portrait lockscreen is shown. activity.setLastReportedConfiguration( new MergedConfiguration(mAtm.getGlobalConfiguration(), rotatedConfig)); - activity.setState(Task.ActivityState.STOPPED, "sleep"); + activity.setState(STOPPED, "sleep"); display.setIsSleeping(true); doReturn(false).when(display).shouldSleep(); @@ -522,7 +523,8 @@ public class RootWindowContainerTests extends WindowTestsBase { final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); final Task targetRootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); - final Task targetTask = new TaskBuilder(mSupervisor).setParentTask(targetRootTask).build(); + final Task targetTask = new TaskBuilder(mSupervisor).setParentTaskFragment(targetRootTask) + .build(); // Create Recents on secondary display. final TestDisplayContent secondDisplay = addNewDisplayContentAt( @@ -557,8 +559,8 @@ public class RootWindowContainerTests extends WindowTestsBase { doReturn(rootTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask(); // Use the task as target to resume. - mRootWindowContainer.resumeFocusedTasksTopActivities( - rootTask, activity, null /* targetOptions */); + mRootWindowContainer.resumeFocusedTasksTopActivities(rootTask, activity, + null /* targetOptions */); // Verify the target task should resume its activity. verify(rootTask, times(1)).resumeTopActivityUncheckedLocked( @@ -626,7 +628,7 @@ public class RootWindowContainerTests extends WindowTestsBase { ACTIVITY_TYPE_STANDARD, false /* onTop */)); final ActivityRecord activity = new ActivityBuilder(mAtm) .setTask(rootTask).setOnTop(true).build(); - activity.setState(Task.ActivityState.RESUMED, "test"); + activity.setState(RESUMED, "test"); // Assume the task is at the topmost position assertTrue(rootTask.isTopRootTaskInDisplayArea()); @@ -646,7 +648,7 @@ public class RootWindowContainerTests extends WindowTestsBase { ACTIVITY_TYPE_STANDARD, false /* onTop */)); final ActivityRecord activity = new ActivityBuilder(mAtm) .setTask(rootTask).setOnTop(true).build(); - activity.setState(Task.ActivityState.RESUMED, "test"); + activity.setState(RESUMED, "test"); taskDisplayArea.positionChildAt(POSITION_BOTTOM, rootTask, false /*includingParents*/); // Assume the task is at the topmost position @@ -774,7 +776,7 @@ public class RootWindowContainerTests extends WindowTestsBase { } /** - * Tests that when starting {@link #ResolverActivity} for home, it should use the standard + * Tests that when starting {@link ResolverActivity} for home, it should use the standard * activity type (in a new root task) so the order of back stack won't be broken. */ @Test @@ -1006,33 +1008,31 @@ public class RootWindowContainerTests extends WindowTestsBase { @Test public void testLockAllProfileTasks() { - // Make an activity visible with the user id set to 0 - final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); - final int taskId = task.mTaskId; - final ActivityRecord activity = task.getTopMostActivity(); - - // Create another activity on top and the user id is 1 - final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task) - .setUid(UserHandle.PER_USER_RANGE + 1).build(); - doReturn(true).when(topActivity).okToShowLocked(); + final int profileUid = UserHandle.PER_USER_RANGE + UserHandle.MIN_SECONDARY_USER_ID; + final int profileUserId = UserHandle.getUserId(profileUid); + // Create an activity belonging to the profile user. + final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true) + .setUid(profileUid).build(); + final Task task = activity.getTask(); + + // Create another activity belonging to current user on top. + final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build(); topActivity.intent.setAction(Intent.ACTION_MAIN); // Make sure the listeners will be notified for putting the task to locked state TaskChangeNotificationController controller = mAtm.getTaskChangeNotificationController(); spyOn(controller); - mWm.mRoot.lockAllProfileTasks(0); - verify(controller).notifyTaskProfileLocked(eq(taskId), eq(0)); + mWm.mRoot.lockAllProfileTasks(profileUserId); + verify(controller).notifyTaskProfileLocked(eq(task.mTaskId), eq(profileUserId)); // Create the work lock activity on top of the task - final ActivityRecord workLockActivity = new ActivityBuilder(mAtm).setTask(task) - .setUid(UserHandle.PER_USER_RANGE + 1).build(); - doReturn(true).when(workLockActivity).okToShowLocked(); + final ActivityRecord workLockActivity = new ActivityBuilder(mAtm).setTask(task).build(); workLockActivity.intent.setAction(ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER); doReturn(workLockActivity.mActivityComponent).when(mAtm).getSysUiServiceComponentLocked(); // Make sure the listener won't be notified again. clearInvocations(controller); - mWm.mRoot.lockAllProfileTasks(0); + mWm.mRoot.lockAllProfileTasks(profileUserId); verify(controller, never()).notifyTaskProfileLocked(anyInt(), anyInt()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java index c44c22fefd7a..cb858845e03e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java @@ -166,7 +166,7 @@ public class RunningTasksTest extends WindowTestsBase { final Task task = new TaskBuilder(mAtm.mTaskSupervisor) .setComponent(new ComponentName(mContext.getPackageName(), className)) .setTaskId(taskId) - .setParentTask(stack) + .setParentTaskFragment(stack) .build(); task.lastActiveTime = lastActiveTime; final ActivityRecord activity = new ActivityBuilder(mAtm) diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index f35e85c3b14c..9639aa78fd5b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -40,8 +40,15 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; +import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS; +import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.ActivityRecord.State.STOPPED; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; -import static com.android.server.wm.Task.ActivityState.STOPPED; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.google.common.truth.Truth.assertThat; @@ -53,7 +60,9 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doCallRealMethod; @@ -74,6 +83,8 @@ import android.view.WindowManager; import androidx.test.filters.MediumTest; +import com.android.internal.policy.SystemBarUtils; + import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; @@ -99,10 +110,14 @@ public class SizeCompatTests extends WindowTestsBase { private Task mTask; private ActivityRecord mActivity; + private ActivityMetricsLogger mActivityMetricsLogger; private Properties mInitialConstrainDisplayApisFlags; @Before public void setUp() throws Exception { + mActivityMetricsLogger = mock(ActivityMetricsLogger.class); + clearInvocations(mActivityMetricsLogger); + doReturn(mActivityMetricsLogger).when(mAtm.mTaskSupervisor).getActivityMetricsLogger(); mInitialConstrainDisplayApisFlags = DeviceConfig.getProperties( NAMESPACE_CONSTRAIN_DISPLAY_APIS); DeviceConfig.setProperties( @@ -130,7 +145,7 @@ public class SizeCompatTests extends WindowTestsBase { doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity); mActivity.mVisibleRequested = true; mActivity.setSavedState(null /* savedState */); - mActivity.setState(Task.ActivityState.RESUMED, "testRestart"); + mActivity.setState(RESUMED, "testRestart"); prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); final Rect originalOverrideBounds = new Rect(mActivity.getBounds()); @@ -138,49 +153,11 @@ public class SizeCompatTests extends WindowTestsBase { // The visible activity should recompute configuration according to the last parent bounds. mAtm.mActivityClientController.restartActivityProcessIfVisible(mActivity.appToken); - assertEquals(Task.ActivityState.RESTARTING_PROCESS, mActivity.getState()); + assertEquals(RESTARTING_PROCESS, mActivity.getState()); assertNotEquals(originalOverrideBounds, mActivity.getBounds()); } @Test - public void testKeepBoundsWhenChangingFromFreeformToFullscreen() { - removeGlobalMinSizeRestriction(); - // Create landscape freeform display and a freeform app. - DisplayContent display = new TestDisplayContent.Builder(mAtm, 2000, 1000) - .setCanRotate(false) - .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM).build(); - setUpApp(display); - - // Put app window into portrait freeform and then make it a compat app. - final Rect bounds = new Rect(100, 100, 400, 600); - mTask.setBounds(bounds); - prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); - assertEquals(bounds, mActivity.getBounds()); - // Activity is not yet in size compat mode; it is filling the freeform task window. - assertActivityMaxBoundsSandboxed(); - - // The activity should be able to accept negative x position [-150, 100 - 150, 600]. - final int dx = bounds.left + bounds.width() / 2; - mTask.setBounds(bounds.left - dx, bounds.top, bounds.right - dx, bounds.bottom); - assertEquals(mTask.getBounds(), mActivity.getBounds()); - - final int density = mActivity.getConfiguration().densityDpi; - - // Change display configuration to fullscreen. - Configuration c = new Configuration(display.getRequestedOverrideConfiguration()); - c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); - display.onRequestedOverrideConfigurationChanged(c); - - // Check if dimensions on screen stay the same by scaling. - assertScaled(); - assertEquals(bounds.width(), mActivity.getBounds().width()); - assertEquals(bounds.height(), mActivity.getBounds().height()); - assertEquals(density, mActivity.getConfiguration().densityDpi); - // Size compat mode is sandboxed at the activity level. - assertActivityMaxBoundsSandboxed(); - } - - @Test public void testFixedAspectRatioBoundsWithDecorInSquareDisplay() { final int notchHeight = 100; setUpApp(new TestDisplayContent.Builder(mAtm, 600, 800).setNotch(notchHeight).build()); @@ -318,6 +295,11 @@ public class SizeCompatTests extends WindowTestsBase { assertScaled(); // Activity is sandboxed due to size compat mode. assertActivityMaxBoundsSandboxed(); + + final WindowState appWindow = addWindowToActivity(mActivity); + assertTrue(mActivity.hasSizeCompatBounds()); + assertEquals("App window must use size compat bounds for layout in screen space", + mActivity.getBounds(), appWindow.getBounds()); } @Test @@ -585,7 +567,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testHandleActivitySizeCompatModeChanged() { setUpDisplaySizeWithApp(1000, 2000); doReturn(true).when(mTask).isOrganized(); - mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged"); + mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged"); prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); assertFitted(); @@ -604,7 +586,7 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mVisibleRequested = true; mActivity.restartProcessIfVisible(); // The full lifecycle isn't hooked up so manually set state to resumed - mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged"); + mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged"); mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(mActivity); // Expect null token when switching to non-size-compat mode activity. @@ -618,7 +600,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testHandleActivitySizeCompatModeChangedOnDifferentTask() { setUpDisplaySizeWithApp(1000, 2000); doReturn(true).when(mTask).isOrganized(); - mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged"); + mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged"); prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); assertFitted(); @@ -637,7 +619,7 @@ public class SizeCompatTests extends WindowTestsBase { .setCreateActivity(true).build(); final ActivityRecord secondActivity = secondTask.getTopNonFinishingActivity(); doReturn(true).when(secondTask).isOrganized(); - secondActivity.setState(Task.ActivityState.RESUMED, + secondActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged"); prepareUnresizable(secondActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); @@ -665,7 +647,7 @@ public class SizeCompatTests extends WindowTestsBase { .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE) .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) .build(); - assertTrue(activity.shouldCreateCompatDisplayInsets()); + assertFalse(activity.shouldCreateCompatDisplayInsets()); // The non-resizable activity should not be size compat because it is on a resizable task // in multi-window mode. @@ -697,7 +679,7 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testShouldCreateCompatDisplayInsetsWhenUnresizeableAndSupportsSizeChangesFalse() { + public void testShouldNotCreateCompatDisplayInsetsWhenRootActivityIsResizeable() { setUpDisplaySizeWithApp(1000, 2500); // Make the task root resizable. @@ -706,7 +688,7 @@ public class SizeCompatTests extends WindowTestsBase { // Create an activity on the same task. final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false, RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - assertTrue(activity.shouldCreateCompatDisplayInsets()); + assertFalse(activity.shouldCreateCompatDisplayInsets()); } @Test @@ -1101,6 +1083,147 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) + public void testOverrideMinAspectRatioScreenOrientationNotSetThenChangedToPortrait() { + // In this test, the activity's orientation isn't fixed to portrait, therefore the override + // isn't applied. + + setUpDisplaySizeWithApp(1000, 1200); + + // Create a size compat activity on the same task. + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + // The per-package override should have no effect + assertEquals(1200, activity.getBounds().height()); + assertEquals(1000, activity.getBounds().width()); + + // After changing the orientation to portrait the override should be applied. + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + activity.clearSizeCompatMode(); + + // The per-package override forces the activity into a 3:2 aspect ratio + assertEquals(1200, activity.getBounds().height()); + assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, + activity.getBounds().width(), 0.5); + } + + @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) + public void testOverrideMinAspectRatioScreenOrientationLandscapeThenChangedToPortrait() { + // In this test, the activity's orientation isn't fixed to portrait, therefore the override + // isn't applied. + + setUpDisplaySizeWithApp(1000, 1200); + + // Create a size compat activity on the same task. + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + // The per-package override should have no effect + assertEquals(1200, activity.getBounds().height()); + assertEquals(1000, activity.getBounds().width()); + + // After changing the orientation to portrait the override should be applied. + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + activity.clearSizeCompatMode(); + + // The per-package override forces the activity into a 3:2 aspect ratio + assertEquals(1200, activity.getBounds().height()); + assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, + activity.getBounds().width(), 0.5); + } + + @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) + public void testOverrideMinAspectRatioScreenOrientationPortraitThenChangedToUnspecified() { + setUpDisplaySizeWithApp(1000, 1200); + + // Create a size compat activity on the same task. + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + // The per-package override forces the activity into a 3:2 aspect ratio + assertEquals(1200, activity.getBounds().height()); + assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, + activity.getBounds().width(), 0.5); + + // After changing the orientation to landscape the override shouldn't be applied. + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + activity.clearSizeCompatMode(); + + // The per-package override should have no effect + assertEquals(1200, activity.getBounds().height()); + assertEquals(1000, activity.getBounds().width()); + } + + @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) + @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY}) + public void testOverrideMinAspectRatioPortraitOnlyDisabledScreenOrientationNotSet() { + setUpDisplaySizeWithApp(1000, 1200); + + // Create a size compat activity on the same task. + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + // The per-package override forces the activity into a 3:2 aspect ratio + assertEquals(1200, activity.getBounds().height()); + assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, + activity.getBounds().width(), 0.5); + } + + @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) + @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY}) + public void testOverrideMinAspectRatioPortraitOnlyDisabledScreenOrientationLandscape() { + // In this test, the activity's orientation isn't fixed to portrait, therefore the override + // isn't applied. + + setUpDisplaySizeWithApp(1000, 1200); + + // Create a size compat activity on the same task. + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + // The per-package override forces the activity into a 3:2 aspect ratio + assertEquals(1000 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, + activity.getBounds().height(), 0.5); + assertEquals(1000, activity.getBounds().width()); + } + + @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) public void testOverrideMinAspectRatioWithoutGlobalOverride() { // In this test, only OVERRIDE_MIN_ASPECT_RATIO_1_5 is set, which has no effect without @@ -1553,6 +1676,30 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testSandboxDisplayApis_unresizableAppNotSandboxed() { + // Set up a display in landscape with an unresizable app. + setUpDisplaySizeWithApp(2500, 1000); + mActivity.mDisplayContent.setSandboxDisplayApis(false /* sandboxDisplayApis */); + prepareUnresizable(mActivity, 1.5f, SCREEN_ORIENTATION_LANDSCAPE); + assertFitted(); + + // Activity max bounds not be sandboxed since sandboxing is disabled. + assertMaxBoundsInheritDisplayAreaBounds(); + } + + @Test + public void testSandboxDisplayApis_unresizableAppSandboxed() { + // Set up a display in landscape with an unresizable app. + setUpDisplaySizeWithApp(2500, 1000); + mActivity.mDisplayContent.setSandboxDisplayApis(true /* sandboxDisplayApis */); + prepareUnresizable(mActivity, 1.5f, SCREEN_ORIENTATION_LANDSCAPE); + assertFitted(); + + // Activity max bounds should be sandboxed since sandboxing is enabled. + assertActivityMaxBoundsSandboxed(); + } + + @Test public void testResizableApp_notSandboxed() { // Set up a display in landscape with a fully resizable app. setUpDisplaySizeWithApp(2500, 1000); @@ -1808,7 +1955,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(0, 0, 700, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(0, 0, 700, 1400), + /* sizeCompatUnscaled */ new Rect(0, 700, 700, 2100), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(0, 0, 350, 700)); } @@ -1821,7 +1968,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), + /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); } @@ -1836,7 +1983,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), + /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); @@ -1846,7 +1993,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), + /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); } @@ -1859,7 +2006,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(2100, 0, 2800, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(700, 0, 1400, 1400), + /* sizeCompatUnscaled */ new Rect(700, 700, 1400, 2100), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(1050, 0, 1400, 700)); } @@ -1908,13 +2055,19 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1000, 2500); assertFalse(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED); prepareUnresizable(mActivity, /* maxAspect= */ 2, SCREEN_ORIENTATION_PORTRAIT); assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); assertTrue(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, + APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO); + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + verifyLogAppCompatState(mActivity, + APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE); rotateDisplay(mActivity.mDisplayContent, ROTATION_0); // After returning to the original rotation, bounds are computed in @@ -1924,6 +2077,18 @@ public class SizeCompatTests extends WindowTestsBase { assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); assertTrue(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, + APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO); + + // After setting the visibility of the activity to false, areBoundsLetterboxed() still + // returns true but the NOT_VISIBLE App Compat state is logged. + mActivity.setVisibility(false); + assertTrue(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE); + mActivity.setVisibility(true); + assertTrue(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, + APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO); } @Test @@ -1932,12 +2097,15 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); assertFalse(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); assertTrue(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, + APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION); } @Test @@ -1947,12 +2115,70 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); assertFalse(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED); rotateDisplay(mActivity.mDisplayContent, ROTATION_90); assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertTrue(mActivity.inSizeCompatMode()); assertTrue(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, + APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE); + } + + /** + * Tests that all three paths in which aspect ratio logic can be applied yield the same + * result, which is that aspect ratio is respected on app bounds. The three paths are + * fixed orientation, no fixed orientation but fixed aspect ratio, and size compat mode. + */ + @Test + public void testAllAspectRatioLogicConsistent() { + // Create display that has all stable insets and does not rotate. Make sure that status bar + // height is greater than notch height so that stable bounds do not equal app bounds. + final int notchHeight = 75; + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600) + .setSystemDecorations(true).setNotch(notchHeight) + .setStatusBarHeight(notchHeight + 20).setCanRotate(false).build(); + + // Create task on test display. + final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build(); + + // Target min aspect ratio must be larger than parent aspect ratio to be applied. + final float targetMinAspectRatio = 3.0f; + + // Create fixed portait activity with min aspect ratio greater than parent aspect ratio. + final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm) + .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .setMinAspectRatio(targetMinAspectRatio).build(); + final Rect fixedOrientationAppBounds = new Rect(fixedOrientationActivity.getConfiguration() + .windowConfiguration.getAppBounds()); + + // Create activity with no fixed orientation and min aspect ratio greater than parent aspect + // ratio. + final ActivityRecord minAspectRatioActivity = new ActivityBuilder(mAtm).setTask(task) + .setMinAspectRatio(targetMinAspectRatio).build(); + final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration() + .windowConfiguration.getAppBounds()); + + // Create unresizeable fixed portait activity with min aspect ratio greater than parent + // aspect ratio. + final ActivityRecord sizeCompatActivity = new ActivityBuilder(mAtm) + .setTask(task).setResizeMode(RESIZE_MODE_UNRESIZEABLE) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .setMinAspectRatio(targetMinAspectRatio).build(); + // Resize display running unresizeable activity to make it enter size compat mode. + resizeDisplay(display, 1800, 1000); + final Rect sizeCompatAppBounds = new Rect(sizeCompatActivity.getConfiguration() + .windowConfiguration.getAppBounds()); + + // Check that aspect ratio of app bounds is equal to the min aspect ratio. + final float delta = 0.01f; + assertEquals(targetMinAspectRatio, ActivityRecord + .computeAspectRatio(fixedOrientationAppBounds), delta); + assertEquals(targetMinAspectRatio, ActivityRecord + .computeAspectRatio(minAspectRatioAppBounds), delta); + assertEquals(targetMinAspectRatio, ActivityRecord + .computeAspectRatio(sizeCompatAppBounds), delta); } private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( @@ -1972,7 +2198,7 @@ public class SizeCompatTests extends WindowTestsBase { assertTrue(mActivity.inSizeCompatMode()); // Activity is in size compat mode but not scaled. - assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds()); + assertEquals(new Rect(0, 1050, 1400, 1750), mActivity.getBounds()); } private static WindowState addWindowToActivity(ActivityRecord activity) { @@ -2005,8 +2231,7 @@ public class SizeCompatTests extends WindowTestsBase { displayContent.mWmService, mock(Session.class), new TestIWindow(), attrs, token); token.addWindow(statusBar); statusBar.setRequestedSize(displayContent.mBaseDisplayWidth, - displayContent.getDisplayUiContext().getResources().getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height)); + SystemBarUtils.getStatusBarHeight(displayContent.getDisplayUiContext())); displayPolicy.addWindowLw(statusBar, attrs); displayPolicy.layoutWindowLw(statusBar, null, displayContent.mDisplayFrames); @@ -2053,6 +2278,12 @@ public class SizeCompatTests extends WindowTestsBase { activity.info.resizeMode = isUnresizable ? RESIZE_MODE_UNRESIZEABLE : RESIZE_MODE_RESIZEABLE; + final Task task = activity.getTask(); + if (task != null) { + // Update the Task resize value as activity will follow the task. + task.mResizeMode = activity.info.resizeMode; + task.getRootActivity().info.resizeMode = activity.info.resizeMode; + } activity.mVisibleRequested = true; if (maxAspect >= 0) { activity.info.setMaxAspectRatio(maxAspect); @@ -2111,6 +2342,11 @@ public class SizeCompatTests extends WindowTestsBase { .isEqualTo(activity.getWindowConfiguration().getBounds()); } + private void verifyLogAppCompatState(ActivityRecord activity, int state) { + verify(mActivityMetricsLogger, atLeastOnce()).logAppCompatState( + argThat(r -> activity == r && r.getAppCompatState() == state)); + } + static Configuration rotateDisplay(DisplayContent display, int rotation) { final Configuration c = new Configuration(); display.getDisplayRotation().setRotation(rotation); diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java index 3a2190d13354..cac948c97b25 100644 --- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java +++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java @@ -178,6 +178,11 @@ public class StubTransaction extends SurfaceControl.Transaction { } @Override + public SurfaceControl.Transaction setDisplayFlags(IBinder displayToken, int flags) { + return this; + } + + @Override public SurfaceControl.Transaction setDisplayProjection(IBinder displayToken, int orientation, Rect layerStackRect, Rect displayRect) { return this; diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java index 7bac3e7b8679..420ea8e63562 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java @@ -62,7 +62,7 @@ public class SyncEngineTests extends WindowTestsBase { public void testTrivialSyncCallback() { TestWindowContainer mockWC = new TestWindowContainer(mWm, false /* waiter */); - BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + final BLASTSyncEngine bse = createTestBLASTSyncEngine(); BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); @@ -90,7 +90,7 @@ public class SyncEngineTests extends WindowTestsBase { public void testWaitingSyncCallback() { TestWindowContainer mockWC = new TestWindowContainer(mWm, true /* waiter */); - BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + final BLASTSyncEngine bse = createTestBLASTSyncEngine(); BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); @@ -114,7 +114,7 @@ public class SyncEngineTests extends WindowTestsBase { public void testInvisibleSyncCallback() { TestWindowContainer mockWC = new TestWindowContainer(mWm, true /* waiter */); - BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + final BLASTSyncEngine bse = createTestBLASTSyncEngine(); BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); @@ -142,7 +142,7 @@ public class SyncEngineTests extends WindowTestsBase { parentWC.addChild(childWC, POSITION_TOP); parentWC.addChild(childWC2, POSITION_TOP); - BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + final BLASTSyncEngine bse = createTestBLASTSyncEngine(); BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); @@ -175,7 +175,7 @@ public class SyncEngineTests extends WindowTestsBase { TestWindowContainer childWC = new TestWindowContainer(mWm, true /* waiter */); parentWC.addChild(childWC, POSITION_TOP); - BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + final BLASTSyncEngine bse = createTestBLASTSyncEngine(); BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); @@ -206,7 +206,7 @@ public class SyncEngineTests extends WindowTestsBase { parentWC.addChild(topChildWC, POSITION_TOP); parentWC.addChild(botChildWC, POSITION_BOTTOM); - BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + final BLASTSyncEngine bse = createTestBLASTSyncEngine(); BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); @@ -238,7 +238,7 @@ public class SyncEngineTests extends WindowTestsBase { parentWC.addChild(topChildWC, POSITION_TOP); parentWC.addChild(botChildWC, POSITION_BOTTOM); - BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + final BLASTSyncEngine bse = createTestBLASTSyncEngine(); BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); @@ -273,7 +273,7 @@ public class SyncEngineTests extends WindowTestsBase { parentWC.addChild(topChildWC, POSITION_TOP); nonMemberParentWC.addChild(botChildWC, POSITION_BOTTOM); - BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + final BLASTSyncEngine bse = createTestBLASTSyncEngine(); BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); @@ -312,7 +312,7 @@ public class SyncEngineTests extends WindowTestsBase { parentWC.addChild(topChildWC, POSITION_TOP); parentWC.addChild(botChildWC, POSITION_BOTTOM); - BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + final BLASTSyncEngine bse = createTestBLASTSyncEngine(); BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 8e7ba4bc3293..5bc45d7c3d17 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -271,7 +271,6 @@ public class SystemServicesTestRule implements TestRule { doNothing().when(amInternal).cleanUpServices(anyInt(), any(), any()); doReturn(UserHandle.USER_SYSTEM).when(amInternal).getCurrentUserId(); doReturn(TEST_USER_PROFILE_IDS).when(amInternal).getCurrentProfileIds(); - doReturn(true).when(amInternal).isCurrentProfile(anyInt()); doReturn(true).when(amInternal).isUserRunning(anyInt(), anyInt()); doReturn(true).when(amInternal).hasStartedUserState(anyInt()); doReturn(false).when(amInternal).shouldConfirmCredentials(anyInt()); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java index 67b273a5a82d..cdf6b59d4737 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java @@ -37,8 +37,8 @@ import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; -import static com.android.server.wm.Task.ActivityState.RESUMED; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.google.common.truth.Truth.assertThat; @@ -84,8 +84,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); adjacentRootTask.mCreatedByOrganizer = true; final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); - adjacentRootTask.mAdjacentTask = rootTask; - rootTask.mAdjacentTask = adjacentRootTask; + adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */); taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask); Task actualRootTask = taskDisplayArea.getLaunchRootTask( @@ -111,8 +110,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { final Task adjacentRootTask = createTask( mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); adjacentRootTask.mCreatedByOrganizer = true; - adjacentRootTask.mAdjacentTask = rootTask; - rootTask.mAdjacentTask = adjacentRootTask; + adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */); taskDisplayArea.setLaunchRootTask(rootTask, new int[]{WINDOWING_MODE_MULTI_WINDOW}, new int[]{ACTIVITY_TYPE_STANDARD}); @@ -133,8 +131,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); adjacentRootTask.mCreatedByOrganizer = true; final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); - adjacentRootTask.mAdjacentTask = rootTask; - rootTask.mAdjacentTask = adjacentRootTask; + adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */); taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask); final Task actualRootTask = taskDisplayArea.getLaunchRootTask( @@ -623,7 +620,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { final Task pinnedRootTask = mRootWindowContainer.getDefaultTaskDisplayArea() .createRootTask(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP); final Task pinnedTask = new TaskBuilder(mAtm.mTaskSupervisor) - .setParentTask(pinnedRootTask).build(); + .setParentTaskFragment(pinnedRootTask).build(); new ActivityBuilder(mAtm).setActivityFlags(FLAG_ALWAYS_FOCUSABLE) .setTask(pinnedTask).build(); pinnedRootTask.moveToFront("movePinnedRootTaskToFront"); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java new file mode 100644 index 000000000000..f8c7207cefa7 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -0,0 +1,526 @@ +/* + * Copyright (C) 2021 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 com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.wm.testing.Assert.assertThrows; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.view.RemoteAnimationDefinition; +import android.view.SurfaceControl; +import android.window.ITaskFragmentOrganizer; +import android.window.TaskFragmentCreationParams; +import android.window.TaskFragmentInfo; +import android.window.TaskFragmentOrganizer; +import android.window.TaskFragmentOrganizerToken; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; +import android.window.WindowContainerTransactionCallback; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Build/Install/Run: + * atest WmTests:TaskFragmentOrganizerControllerTest + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { + + private TaskFragmentOrganizerController mController; + private TaskFragmentOrganizer mOrganizer; + private TaskFragmentOrganizerToken mOrganizerToken; + private ITaskFragmentOrganizer mIOrganizer; + private TaskFragment mTaskFragment; + private TaskFragmentInfo mTaskFragmentInfo; + private IBinder mFragmentToken; + private WindowContainerTransaction mTransaction; + private WindowContainerToken mFragmentWindowToken; + private RemoteAnimationDefinition mDefinition; + + @Before + public void setup() { + mController = mAtm.mWindowOrganizerController.mTaskFragmentOrganizerController; + mOrganizer = new TaskFragmentOrganizer(Runnable::run); + mOrganizerToken = mOrganizer.getOrganizerToken(); + mIOrganizer = ITaskFragmentOrganizer.Stub.asInterface(mOrganizerToken.asBinder()); + mTaskFragmentInfo = mock(TaskFragmentInfo.class); + mFragmentToken = new Binder(); + mTaskFragment = + new TaskFragment(mAtm, mFragmentToken, true /* createdByOrganizer */); + mTransaction = new WindowContainerTransaction(); + mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken(); + mDefinition = new RemoteAnimationDefinition(); + + spyOn(mController); + spyOn(mOrganizer); + spyOn(mTaskFragment); + doReturn(mIOrganizer).when(mTaskFragment).getTaskFragmentOrganizer(); + doReturn(mTaskFragmentInfo).when(mTaskFragment).getTaskFragmentInfo(); + doReturn(new SurfaceControl()).when(mTaskFragment).getSurfaceControl(); + doReturn(mFragmentToken).when(mTaskFragment).getFragmentToken(); + doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration(); + } + + @Test + public void testCallTaskFragmentCallbackWithoutRegister_throwsException() { + assertThrows(IllegalArgumentException.class, () -> mController + .onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment)); + + assertThrows(IllegalArgumentException.class, () -> mController + .onTaskFragmentInfoChanged( + mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment)); + + assertThrows(IllegalArgumentException.class, () -> mController + .onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment)); + + assertThrows(IllegalArgumentException.class, () -> mController + .onTaskFragmentParentInfoChanged(mTaskFragment.getTaskFragmentOrganizer(), + mTaskFragment)); + } + + @Test + public void testOnTaskFragmentAppeared() { + mController.registerOrganizer(mIOrganizer); + + mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + + verify(mOrganizer).onTaskFragmentAppeared(any()); + } + + @Test + public void testOnTaskFragmentInfoChanged() { + mController.registerOrganizer(mIOrganizer); + mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + + // No callback if the info is not changed. + doReturn(true).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any()); + doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration(); + + mController.onTaskFragmentInfoChanged(mTaskFragment.getTaskFragmentOrganizer(), + mTaskFragment); + mController.dispatchPendingEvents(); + + verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); + + // Trigger callback if the info is changed. + doReturn(false).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any()); + + mController.onTaskFragmentInfoChanged(mTaskFragment.getTaskFragmentOrganizer(), + mTaskFragment); + mController.dispatchPendingEvents(); + + verify(mOrganizer).onTaskFragmentInfoChanged(mTaskFragmentInfo); + } + + @Test + public void testOnTaskFragmentVanished() { + mController.registerOrganizer(mIOrganizer); + + mTaskFragment.mTaskFragmentAppearedSent = true; + mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + + verify(mOrganizer).onTaskFragmentVanished(any()); + } + + @Test + public void testOnTaskFragmentParentInfoChanged() { + mController.registerOrganizer(mIOrganizer); + final Task parent = mock(Task.class); + final Configuration parentConfig = new Configuration(); + parentConfig.smallestScreenWidthDp = 10; + doReturn(parent).when(mTaskFragment).getParent(); + doReturn(parentConfig).when(parent).getConfiguration(); + doReturn(parent).when(parent).asTask(); + + mTaskFragment.mTaskFragmentAppearedSent = true; + mController.onTaskFragmentParentInfoChanged( + mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + + verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mFragmentToken), any()); + + // No extra callback if the info is not changed. + clearInvocations(mOrganizer); + + mController.onTaskFragmentParentInfoChanged( + mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + + verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), any()); + + // Trigger callback if the info is changed. + parentConfig.smallestScreenWidthDp = 100; + + mController.onTaskFragmentParentInfoChanged( + mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + + verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mFragmentToken), any()); + } + + @Test + public void testOnTaskFragmentError() throws RemoteException { + final IBinder errorCallbackToken = new Binder(); + final Throwable exception = new IllegalArgumentException("Test exception"); + + mController.registerOrganizer(mIOrganizer); + mController.onTaskFragmentError(mTaskFragment.getTaskFragmentOrganizer(), + errorCallbackToken, exception); + mController.dispatchPendingEvents(); + + verify(mOrganizer).onTaskFragmentError(eq(errorCallbackToken), eq(exception)); + } + + @Test + public void testRegisterRemoteAnimations() { + mController.registerOrganizer(mIOrganizer); + mController.registerRemoteAnimations(mIOrganizer, mDefinition); + + assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer)); + + mController.unregisterRemoteAnimations(mIOrganizer); + + assertNull(mController.getRemoteAnimationDefinition(mIOrganizer)); + } + + @Test + public void testWindowContainerTransaction_setTaskFragmentOrganizer() { + mOrganizer.applyTransaction(mTransaction); + + assertEquals(mIOrganizer, mTransaction.getTaskFragmentOrganizer()); + + mTransaction = new WindowContainerTransaction(); + mOrganizer.applySyncTransaction( + mTransaction, mock(WindowContainerTransactionCallback.class)); + + assertEquals(mIOrganizer, mTransaction.getTaskFragmentOrganizer()); + } + + @Test + public void testApplyTransaction_enforceConfigurationChangeOnOrganizedTaskFragment() + throws RemoteException { + mOrganizer.applyTransaction(mTransaction); + + // Throw exception if the transaction is trying to change a window that is not organized by + // the organizer. + mTransaction.setBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100)); + + assertThrows(SecurityException.class, () -> { + try { + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + } catch (RemoteException e) { + fail(); + } + }); + + // Allow transaction to change a TaskFragment created by the organizer. + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); + + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + } + + @Test + public void testApplyTransaction_enforceHierarchyChange_reorder() throws RemoteException { + mOrganizer.applyTransaction(mTransaction); + + // Throw exception if the transaction is trying to change a window that is not organized by + // the organizer. + mTransaction.reorder(mFragmentWindowToken, true /* onTop */); + + assertThrows(SecurityException.class, () -> { + try { + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + } catch (RemoteException e) { + fail(); + } + }); + + // Allow transaction to change a TaskFragment created by the organizer. + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); + + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + } + + @Test + public void testApplyTransaction_enforceHierarchyChange_deleteTaskFragment() + throws RemoteException { + mController.registerOrganizer(mIOrganizer); + mOrganizer.applyTransaction(mTransaction); + doReturn(true).when(mTaskFragment).isAttached(); + + // Throw exception if the transaction is trying to change a window that is not organized by + // the organizer. + mTransaction.deleteTaskFragment(mFragmentWindowToken); + + assertThrows(SecurityException.class, () -> { + try { + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + } catch (RemoteException e) { + fail(); + } + }); + + // Allow transaction to change a TaskFragment created by the organizer. + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); + clearInvocations(mAtm.mRootWindowContainer); + + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + // No lifecycle update when the TaskFragment is not recorded. + verify(mAtm.mRootWindowContainer, never()).resumeFocusedTasksTopActivities(); + + mAtm.mWindowOrganizerController.mLaunchTaskFragments + .put(mFragmentToken, mTaskFragment); + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); + } + + @Test + public void testApplyTransaction_enforceHierarchyChange_setAdjacentRoots() + throws RemoteException { + final TaskFragment taskFragment2 = + new TaskFragment(mAtm, new Binder(), true /* createdByOrganizer */); + final WindowContainerToken token2 = taskFragment2.mRemoteToken.toWindowContainerToken(); + mOrganizer.applyTransaction(mTransaction); + + // Throw exception if the transaction is trying to change a window that is not organized by + // the organizer. + mTransaction.setAdjacentRoots(mFragmentWindowToken, token2, false /* moveTogether */); + + assertThrows(SecurityException.class, () -> { + try { + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + } catch (RemoteException e) { + fail(); + } + }); + + // Allow transaction to change a TaskFragment created by the organizer. + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); + taskFragment2.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); + clearInvocations(mAtm.mRootWindowContainer); + + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); + } + + @Test + public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() + throws RemoteException { + mController.registerOrganizer(mIOrganizer); + final ActivityRecord activity = createActivityRecord(mDisplayContent); + final int uid = Binder.getCallingUid(); + activity.info.applicationInfo.uid = uid; + activity.getTask().effectiveUid = uid; + final IBinder fragmentToken = new Binder(); + final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder( + mOrganizerToken, fragmentToken, activity.token).build(); + mOrganizer.applyTransaction(mTransaction); + + // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment. + mTransaction.createTaskFragment(params); + mTransaction.startActivityInTaskFragment( + mFragmentToken, null /* callerToken */, new Intent(), null /* activityOptions */); + mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class)); + mTransaction.setAdjacentTaskFragments(mFragmentToken, mock(IBinder.class), + null /* options */); + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + // Successfully created a TaskFragment. + final TaskFragment taskFragment = mAtm.mWindowOrganizerController + .getTaskFragment(fragmentToken); + assertNotNull(taskFragment); + assertEquals(activity.getTask(), taskFragment.getTask()); + } + + @Test + public void testApplyTransaction_createTaskFragment_failForDifferentUid() + throws RemoteException { + mController.registerOrganizer(mIOrganizer); + final ActivityRecord activity = createActivityRecord(mDisplayContent); + final int uid = Binder.getCallingUid(); + final IBinder fragmentToken = new Binder(); + final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder( + mOrganizerToken, fragmentToken, activity.token).build(); + mOrganizer.applyTransaction(mTransaction); + mTransaction.createTaskFragment(params); + + // Fail to create TaskFragment when the task uid is different from caller. + activity.info.applicationInfo.uid = uid; + activity.getTask().effectiveUid = uid + 1; + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken)); + + // Fail to create TaskFragment when the task uid is different from owner activity. + activity.info.applicationInfo.uid = uid + 1; + activity.getTask().effectiveUid = uid; + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken)); + + // Successfully created a TaskFragment for same uid. + activity.info.applicationInfo.uid = uid; + activity.getTask().effectiveUid = uid; + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken)); + } + + @Test + public void testApplyTransaction_enforceHierarchyChange_reparentChildren() + throws RemoteException { + mOrganizer.applyTransaction(mTransaction); + mController.registerOrganizer(mIOrganizer); + doReturn(true).when(mTaskFragment).isAttached(); + + // Throw exception if the transaction is trying to change a window that is not organized by + // the organizer. + mTransaction.reparentChildren(mFragmentWindowToken, null /* newParent */); + + assertThrows(SecurityException.class, () -> { + try { + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + } catch (RemoteException e) { + fail(); + } + }); + + // Allow transaction to change a TaskFragment created by the organizer. + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); + clearInvocations(mAtm.mRootWindowContainer); + + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); + } + + @Test + public void testApplyTransaction_reparentActivityToTaskFragment_triggerLifecycleUpdate() + throws RemoteException { + final ActivityRecord activity = createActivityRecord(mDefaultDisplay); + mOrganizer.applyTransaction(mTransaction); + mController.registerOrganizer(mIOrganizer); + mTaskFragment = new TaskFragmentBuilder(mAtm) + .setCreateParentTask() + .setFragmentToken(mFragmentToken) + .build(); + mAtm.mWindowOrganizerController.mLaunchTaskFragments + .put(mFragmentToken, mTaskFragment); + mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.appToken); + clearInvocations(mAtm.mRootWindowContainer); + + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); + } + + @Test + public void testDeferPendingTaskFragmentEventsOfInvisibleTask() { + // Task - TaskFragment - Activity. + final Task task = createTask(mDisplayContent); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(mOrganizer) + .setFragmentToken(mFragmentToken) + .build(); + + // Mock the task to invisible + doReturn(false).when(task).shouldBeVisible(any()); + + // Sending events + mController.registerOrganizer(mIOrganizer); + taskFragment.mTaskFragmentAppearedSent = true; + mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); + mController.dispatchPendingEvents(); + + // Verifies that event was not sent + verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); + } + + /** + * Tests that a task fragment info changed event is still sent if the task is invisible only + * when the info changed event is because of the last activity in a task finishing. + */ + @Test + public void testLastPendingTaskFragmentInfoChangedEventOfInvisibleTaskSent() { + // Create a TaskFragment with an activity, all within a parent task + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setOrganizer(mOrganizer) + .setFragmentToken(mFragmentToken) + .setCreateParentTask() + .createActivityCount(1) + .build(); + final Task parentTask = taskFragment.getTask(); + final ActivityRecord activity = taskFragment.getTopNonFinishingActivity(); + assertTrue(parentTask.shouldBeVisible(null)); + + // Dispatch pending info changed event from creating the activity + mController.registerOrganizer(mIOrganizer); + taskFragment.mTaskFragmentAppearedSent = true; + mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); + mController.dispatchPendingEvents(); + + // Finish the activity and verify that the task is invisible + activity.finishing = true; + assertFalse(parentTask.shouldBeVisible(null)); + + // Verify the info changed callback still occurred despite the task being invisible + reset(mOrganizer); + mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); + mController.dispatchPendingEvents(); + verify(mOrganizer).onTaskFragmentInfoChanged(any()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java new file mode 100644 index 000000000000..730275cde40b --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2021 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 com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.clearInvocations; + +import android.graphics.Rect; +import android.os.Binder; +import android.platform.test.annotations.Presubmit; +import android.view.SurfaceControl; +import android.window.ITaskFragmentOrganizer; +import android.window.TaskFragmentInfo; +import android.window.TaskFragmentOrganizer; + +import androidx.test.filters.MediumTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for {@link TaskFragment}. + * + * Build/Install/Run: + * atest WmTests:TaskFragmentTest + */ +@MediumTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class TaskFragmentTest extends WindowTestsBase { + + private TaskFragmentOrganizer mOrganizer; + private TaskFragment mTaskFragment; + private SurfaceControl mLeash; + @Mock + private SurfaceControl.Transaction mTransaction; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mOrganizer = new TaskFragmentOrganizer(Runnable::run); + final ITaskFragmentOrganizer iOrganizer = + ITaskFragmentOrganizer.Stub.asInterface(mOrganizer.getOrganizerToken().asBinder()); + mAtm.mWindowOrganizerController.mTaskFragmentOrganizerController + .registerOrganizer(iOrganizer); + mTaskFragment = new TaskFragmentBuilder(mAtm) + .setCreateParentTask() + .setOrganizer(mOrganizer) + .setFragmentToken(new Binder()) + .build(); + mLeash = mTaskFragment.getSurfaceControl(); + spyOn(mTaskFragment); + doReturn(mTransaction).when(mTaskFragment).getSyncTransaction(); + doReturn(mTransaction).when(mTaskFragment).getPendingTransaction(); + } + + @Test + public void testOnConfigurationChanged_updateSurface() { + final Rect bounds = new Rect(100, 100, 1100, 1100); + mTaskFragment.setBounds(bounds); + + verify(mTransaction).setPosition(mLeash, 100, 100); + verify(mTransaction).setWindowCrop(mLeash, 1000, 1000); + } + + @Test + public void testStartChangeTransition_resetSurface() { + mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer); + final Rect startBounds = new Rect(0, 0, 1000, 1000); + final Rect endBounds = new Rect(500, 500, 1000, 1000); + mTaskFragment.setBounds(startBounds); + doReturn(true).when(mTaskFragment).isVisible(); + doReturn(true).when(mTaskFragment).isVisibleRequested(); + + clearInvocations(mTransaction); + mTaskFragment.setBounds(endBounds); + + // Surface reset when prepare transition. + verify(mTaskFragment).initializeChangeTransition(startBounds); + verify(mTransaction).setPosition(mLeash, 0, 0); + verify(mTransaction).setWindowCrop(mLeash, 0, 0); + + clearInvocations(mTransaction); + mTaskFragment.mSurfaceFreezer.unfreeze(mTransaction); + + // Update surface after animation. + verify(mTransaction).setPosition(mLeash, 500, 500); + verify(mTransaction).setWindowCrop(mLeash, 500, 500); + } + + @Test + public void testNotOkToAnimate_doNotStartChangeTransition() { + mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer); + final Rect startBounds = new Rect(0, 0, 1000, 1000); + final Rect endBounds = new Rect(500, 500, 1000, 1000); + mTaskFragment.setBounds(startBounds); + doReturn(true).when(mTaskFragment).isVisible(); + doReturn(true).when(mTaskFragment).isVisibleRequested(); + + final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); + displayPolicy.screenTurnedOff(); + + assertFalse(mTaskFragment.okToAnimate()); + + mTaskFragment.setBounds(endBounds); + + verify(mTaskFragment, never()).initializeChangeTransition(any()); + } + + /** + * Tests that when a {@link TaskFragmentInfo} is generated from a {@link TaskFragment}, an + * activity that has not yet been attached to a process because it is being initialized but + * belongs to the TaskFragmentOrganizer process is still reported in the TaskFragmentInfo. + */ + @Test + public void testActivityStillReported_NotYetAssignedToProcess() { + mTaskFragment.addChild(new ActivityBuilder(mAtm).setUid(DEFAULT_TASK_FRAGMENT_ORGANIZER_UID) + .setProcessName(DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME).build()); + final ActivityRecord activity = mTaskFragment.getTopMostActivity(); + // Remove the process to simulate an activity that has not yet been attached to a process + activity.app = null; + final TaskFragmentInfo info = activity.getTaskFragment().getTaskFragmentInfo(); + assertEquals(1, info.getRunningActivityCount()); + assertEquals(1, info.getActivities().size()); + assertEquals(false, info.isEmpty()); + assertEquals(activity.token, info.getActivities().get(0)); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index 5e4c67ce9e5c..168c250a8c93 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -1321,6 +1321,50 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { } @Test + public void testDefaultFreeformSizeRespectsMinAspectRatio() { + final TestDisplayContent freeformDisplay = createNewDisplayContent( + WINDOWING_MODE_FREEFORM); + + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(freeformDisplay.mDisplayId); + + mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP; + mActivity.info.setMinAspectRatio(5f); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder() + .setOptions(options).calculate()); + + final float aspectRatio = + (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height()) + / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height()); + assertTrue("Bounds aspect ratio should be at least 5.0, but was " + aspectRatio, + aspectRatio >= 5f); + } + + @Test + public void testDefaultFreeformSizeRespectsMaxAspectRatio() { + final TestDisplayContent freeformDisplay = createNewDisplayContent( + WINDOWING_MODE_FREEFORM); + + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(freeformDisplay.mDisplayId); + + mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP; + mActivity.info.setMaxAspectRatio(1.5f); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder() + .setOptions(options).calculate()); + + final float aspectRatio = + (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height()) + / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height()); + assertTrue("Bounds aspect ratio should be at most 1.5, but was " + aspectRatio, + aspectRatio <= 1.5f); + } + + @Test public void testCascadesToSourceSizeForFreeform() { final TestDisplayContent freeformDisplay = createNewDisplayContent( WINDOWING_MODE_FREEFORM); @@ -1348,6 +1392,72 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { } @Test + public void testCascadesToSourceSizeForFreeformRespectingMinAspectRatio() { + final TestDisplayContent freeformDisplay = createNewDisplayContent( + WINDOWING_MODE_FREEFORM); + + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(freeformDisplay.mDisplayId); + + final ActivityRecord source = createSourceActivity(freeformDisplay); + source.setBounds(0, 0, 412, 732); + + mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP; + mActivity.info.setMinAspectRatio(5f); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder().setSource(source).setOptions(options).calculate()); + + final Rect displayBounds = freeformDisplay.getBounds(); + assertTrue("Left bounds should be larger than 0.", mResult.mBounds.left > 0); + assertTrue("Top bounds should be larger than 0.", mResult.mBounds.top > 0); + assertTrue("Bounds should be centered at somewhere in the left half, but it's " + + "centerX is " + mResult.mBounds.centerX(), + mResult.mBounds.centerX() < displayBounds.centerX()); + assertTrue("Bounds should be centered at somewhere in the top half, but it's " + + "centerY is " + mResult.mBounds.centerY(), + mResult.mBounds.centerY() < displayBounds.centerY()); + final float aspectRatio = + (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height()) + / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height()); + assertTrue("Bounds aspect ratio should be at least 5.0, but was " + aspectRatio, + aspectRatio >= 5f); + } + + @Test + public void testCascadesToSourceSizeForFreeformRespectingMaxAspectRatio() { + final TestDisplayContent freeformDisplay = createNewDisplayContent( + WINDOWING_MODE_FREEFORM); + + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(freeformDisplay.mDisplayId); + + final ActivityRecord source = createSourceActivity(freeformDisplay); + source.setBounds(0, 0, 412, 732); + + mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP; + mActivity.info.setMaxAspectRatio(1.5f); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder().setSource(source).setOptions(options).calculate()); + + final Rect displayBounds = freeformDisplay.getBounds(); + assertTrue("Left bounds should be larger than 0.", mResult.mBounds.left > 0); + assertTrue("Top bounds should be larger than 0.", mResult.mBounds.top > 0); + assertTrue("Bounds should be centered at somewhere in the left half, but it's " + + "centerX is " + mResult.mBounds.centerX(), + mResult.mBounds.centerX() < displayBounds.centerX()); + assertTrue("Bounds should be centered at somewhere in the top half, but it's " + + "centerY is " + mResult.mBounds.centerY(), + mResult.mBounds.centerY() < displayBounds.centerY()); + final float aspectRatio = + (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height()) + / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height()); + assertTrue("Bounds aspect ratio should be at most 1.5, but was " + aspectRatio, + aspectRatio <= 1.5f); + } + + @Test public void testAdjustBoundsToFitDisplay_TopLeftOutOfDisplay() { final TestDisplayContent freeformDisplay = createNewDisplayContent( WINDOWING_MODE_FREEFORM); @@ -1716,7 +1826,7 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { final Task rootTask = display.getDefaultTaskDisplayArea() .createRootTask(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true); rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM); - final Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build(); + final Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build(); // Just work around the unnecessary adjustments for bounds. task.getWindowConfiguration().setBounds(bounds); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java index 97afc165624a..f573b70352af 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java @@ -144,7 +144,8 @@ public class TaskSnapshotControllerTest extends WindowTestsBase { final int orientation = Configuration.ORIENTATION_PORTRAIT; final float scaleFraction = 0.25f; final Rect contentInsets = new Rect(1, 2, 3, 4); - final Point taskSize = new Point(5, 6); + final Rect letterboxInsets = new Rect(5, 6, 7, 8); + final Point taskSize = new Point(9, 10); try { TaskSnapshot.Builder builder = @@ -156,6 +157,7 @@ public class TaskSnapshotControllerTest extends WindowTestsBase { builder.setColorSpace(sRGB); builder.setOrientation(orientation); builder.setContentInsets(contentInsets); + builder.setLetterboxInsets(letterboxInsets); builder.setIsTranslucent(true); builder.setSnapshot(buffer); builder.setIsRealSnapshot(true); @@ -176,6 +178,7 @@ public class TaskSnapshotControllerTest extends WindowTestsBase { assertFalse(snapshot.isLowResolution()); assertEquals(orientation, snapshot.getOrientation()); assertEquals(contentInsets, snapshot.getContentInsets()); + assertEquals(letterboxInsets, snapshot.getLetterboxInsets()); assertTrue(snapshot.isTranslucent()); assertSame(buffer, snapshot.getHardwareBuffer()); assertTrue(snapshot.isRealSnapshot()); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java index b5219fda1cc8..b8ac0be250a4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java @@ -59,7 +59,8 @@ import java.util.function.Predicate; */ class TaskSnapshotPersisterTestBase extends WindowTestsBase { - private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40); + private static final Rect TEST_CONTENT_INSETS = new Rect(10, 20, 30, 40); + private static final Rect TEST_LETTERBOX_INSETS = new Rect(); static final File FILES_DIR = getInstrumentation().getTargetContext().getFilesDir(); static final long MOCK_SNAPSHOT_ID = 12345678; @@ -208,7 +209,7 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase { return new TaskSnapshot(MOCK_SNAPSHOT_ID, mTopActivityComponent, HardwareBuffer.createFromGraphicBuffer(buffer), ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, - mRotation, taskSize, TEST_INSETS, + mRotation, taskSize, TEST_CONTENT_INSETS, TEST_LETTERBOX_INSETS, // When building a TaskSnapshot with the Builder class, isLowResolution // is always false. Low-res snapshots are only created when loading from // disk. diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java index 9372530f0e80..294aad237977 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java @@ -100,7 +100,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { System.currentTimeMillis(), new ComponentName("", ""), buffer, ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, - Surface.ROTATION_0, taskSize, contentInsets, false, + Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets*/, false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 0ebff1d253ef..4ba3f63f5e63 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -44,6 +44,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; +import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; import static com.google.common.truth.Truth.assertThat; @@ -59,12 +60,14 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.TaskInfo; import android.app.WindowConfiguration; import android.content.ComponentName; @@ -126,8 +129,15 @@ public class TaskTests extends WindowTestsBase { final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); - task.removeIfPossible(); - // Assert that the container was removed. + task.remove(false /* withTransition */, "testRemoveContainer"); + // There is still an activity to be destroyed, so the task is not removed immediately. + assertNotNull(task.getParent()); + assertTrue(rootTask.hasChild()); + assertTrue(task.hasChild()); + assertTrue(activity.finishing); + + activity.destroyed("testRemoveContainer"); + // Assert that the container was removed after the activity is destroyed. assertNull(task.getParent()); assertEquals(0, task.getChildCount()); assertNull(activity.getParent()); @@ -420,7 +430,7 @@ public class TaskTests extends WindowTestsBase { TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea(); Task rootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); - Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build(); + Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build(); final Configuration parentConfig = rootTask.getConfiguration(); parentConfig.windowConfiguration.setBounds(parentBounds); parentConfig.densityDpi = DisplayMetrics.DENSITY_DEFAULT; @@ -750,7 +760,7 @@ public class TaskTests extends WindowTestsBase { DisplayInfo displayInfo = new DisplayInfo(); mAtm.mContext.getDisplay().getDisplayInfo(displayInfo); final int displayHeight = displayInfo.logicalHeight; - final Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build(); + final Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build(); final Configuration inOutConfig = new Configuration(); final Configuration parentConfig = new Configuration(); final int longSide = 1200; @@ -895,10 +905,10 @@ public class TaskTests extends WindowTestsBase { */ @Test public void testFindRootIndex_effectiveRoot_finishingAndRelinquishing() { - final Task task = getTestTask(); + final ActivityRecord activity0 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final Task task = activity0.getTask(); // Add extra two activities. Mark the one on the bottom with "relinquishTaskIdentity" and // one above as finishing. - final ActivityRecord activity0 = task.getBottomMostActivity(); activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); activity1.finishing = true; @@ -930,9 +940,9 @@ public class TaskTests extends WindowTestsBase { */ @Test public void testFindRootIndex_effectiveRoot_relinquishingMultipleActivities() { - final Task task = getTestTask(); + final ActivityRecord activity0 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final Task task = activity0.getTask(); // Set relinquishTaskIdentity for all activities in the task - final ActivityRecord activity0 = task.getBottomMostActivity(); activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; @@ -1082,9 +1092,9 @@ public class TaskTests extends WindowTestsBase { */ @Test public void testGetTaskForActivity_onlyRoot_relinquishTaskIdentity() { - final Task task = getTestTask(); + final ActivityRecord activity0 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final Task task = activity0.getTask(); // Make the current root activity relinquish task identity - final ActivityRecord activity0 = task.getBottomMostActivity(); activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; // Add an extra activity on top - this will be the new root final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); @@ -1180,6 +1190,46 @@ public class TaskTests extends WindowTestsBase { verify(task).setIntent(eq(activity0)); } + /** + * Test {@link Task#updateEffectiveIntent()} when activity with relinquishTaskIdentity but + * another with different uid. This should make the task use the root activity when updating the + * intent. + */ + @Test + public void testUpdateEffectiveIntent_relinquishingWithDifferentUid() { + final ActivityRecord activity0 = new ActivityBuilder(mAtm) + .setActivityFlags(FLAG_RELINQUISH_TASK_IDENTITY).setCreateTask(true).build(); + final Task task = activity0.getTask(); + + // Add an extra activity on top + new ActivityBuilder(mAtm).setUid(11).setTask(task).build(); + + spyOn(task); + task.updateEffectiveIntent(); + verify(task).setIntent(eq(activity0)); + } + + /** + * Test {@link Task#updateEffectiveIntent()} with activities set as relinquishTaskIdentity. + * This should make the task use the topmost activity when updating the intent. + */ + @Test + public void testUpdateEffectiveIntent_relinquishingMultipleActivities() { + final ActivityRecord activity0 = new ActivityBuilder(mAtm) + .setActivityFlags(FLAG_RELINQUISH_TASK_IDENTITY).setCreateTask(true).build(); + final Task task = activity0.getTask(); + // Add an extra activity on top + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); + activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; + + // Add an extra activity on top + final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build(); + + spyOn(task); + task.updateEffectiveIntent(); + verify(task).setIntent(eq(activity2)); + } + @Test public void testSaveLaunchingStateWhenConfigurationChanged() { LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; @@ -1257,7 +1307,8 @@ public class TaskTests extends WindowTestsBase { LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; spyOn(persister); - final Task task = getTestTask(); + final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setCreateParentTask(true).build().getRootTask(); task.setHasBeenVisible(false); task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); @@ -1356,6 +1407,48 @@ public class TaskTests extends WindowTestsBase { assertNotNull(activity.getTask().getDimmer()); } + @Test + public void testMoveToFront_moveAdjacentTask() { + final Task task1 = + createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); + final Task task2 = + createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); + spyOn(task2); + + task1.setAdjacentTaskFragment(task2, false /* moveTogether */); + task1.moveToFront("" /* reason */); + verify(task2, never()).moveToFrontInner(anyString(), isNull()); + + // Reset adjacent tasks to move together. + task1.setAdjacentTaskFragment(null, false /* moveTogether */); + task1.setAdjacentTaskFragment(task2, true /* moveTogether */); + task1.moveToFront("" /* reason */); + verify(task2).moveToFrontInner(anyString(), isNull()); + } + + @Test + public void testResumeTask_doNotResumeTaskFragmentBehindTranslucent() { + final Task task = createTask(mDisplayContent); + final TaskFragment tfBehind = createTaskFragmentWithParentTask( + task, false /* createEmbeddedTask */); + final TaskFragment tfFront = createTaskFragmentWithParentTask( + task, false /* createEmbeddedTask */); + spyOn(tfFront); + doReturn(true).when(tfFront).isTranslucent(any()); + + // TaskFragment behind another translucent TaskFragment should not be resumed. + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, + tfBehind.getVisibility(null /* starting */)); + assertTrue(tfBehind.isFocusable()); + assertFalse(tfBehind.canBeResumed(null /* starting */)); + + spyOn(tfBehind); + task.resumeTopActivityUncheckedLocked(null /* prev */, ActivityOptions.makeBasic(), + false /* deferPause */); + + verify(tfBehind, never()).resumeTopActivity(any(), any(), anyBoolean()); + } + private Task getTestTask() { final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); return task.getBottomMostTask(); @@ -1367,7 +1460,7 @@ public class TaskTests extends WindowTestsBase { TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea(); Task rootTask = taskDisplayArea.createRootTask(windowingMode, ACTIVITY_TYPE_STANDARD, true /* onTop */); - Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build(); + Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build(); final Configuration parentConfig = rootTask.getConfiguration(); parentConfig.windowConfiguration.setAppBounds(parentBounds); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index ce2d74859931..e0f69d4f2eaf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -19,7 +19,9 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -77,6 +79,7 @@ class TestDisplayContent extends DisplayContent { private int mPosition = POSITION_BOTTOM; protected final ActivityTaskManagerService mService; private boolean mSystemDecorations = false; + private int mStatusBarHeight = 0; Builder(ActivityTaskManagerService service, int width, int height) { mService = service; @@ -125,6 +128,10 @@ class TestDisplayContent extends DisplayContent { Insets.of(0, height, 0, 0), null, new Rect(20, 0, 80, height), null, null); return this; } + Builder setStatusBarHeight(int height) { + mStatusBarHeight = height; + return this; + } Builder setCanRotate(boolean canRotate) { mCanRotate = canRotate; return this; @@ -158,6 +165,19 @@ class TestDisplayContent extends DisplayContent { doReturn(false).when(displayPolicy).hasStatusBar(); doReturn(false).when(newDisplay).supportsSystemDecorations(); } + // Update the display policy to make the screen fully turned on so animation is allowed + displayPolicy.screenTurnedOn(null /* screenOnListener */); + displayPolicy.finishKeyguardDrawn(); + displayPolicy.finishWindowsDrawn(); + displayPolicy.finishScreenTurningOn(); + if (mStatusBarHeight > 0) { + doReturn(true).when(displayPolicy).hasStatusBar(); + doAnswer(invocation -> { + Rect inOutInsets = (Rect) invocation.getArgument(0); + inOutInsets.top = mStatusBarHeight; + return null; + }).when(displayPolicy).convertNonDecorInsetsToStableInsets(any(), anyInt()); + } Configuration c = new Configuration(); newDisplay.computeScreenConfiguration(c); c.windowConfiguration.setWindowingMode(mWindowingMode); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index acadb74d333f..9001578cf37a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -128,10 +128,6 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public void setKeyguardCandidateLw(WindowState win) { - } - - @Override public Animation createHiddenByKeyguardExit(boolean onWallpaper, boolean goingToNotificationShade, boolean subtleAnimation) { return null; @@ -368,7 +364,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public int applyKeyguardOcclusionChange() { + public int applyKeyguardOcclusionChange(boolean keyguardOccludingStarted) { return 0; } diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 2dfb3a1a84bc..d9a166a62673 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -20,7 +20,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; +import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; @@ -33,14 +33,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import android.util.ArraySet; +import android.view.SurfaceControl; import android.window.ITaskOrganizer; +import android.window.ITransitionPlayer; import android.window.TransitionInfo; import androidx.test.filters.SmallTest; @@ -48,9 +53,12 @@ import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + /** * Build/Install/Run: - * atest WmTests:TransitionRecordTests + * atest WmTests:TransitionTests */ @SmallTest @Presubmit @@ -59,13 +67,13 @@ public class TransitionTests extends WindowTestsBase { private Transition createTestTransition(int transitType) { TransitionController controller = mock(TransitionController.class); - BLASTSyncEngine sync = new BLASTSyncEngine(mWm); - return new Transition(transitType, 0 /* flags */, controller, sync); + final BLASTSyncEngine sync = createTestBLASTSyncEngine(); + return new Transition(transitType, 0 /* flags */, 0 /* timeoutMs */, controller, sync); } @Test public void testCreateInfo_NewTask() { - final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN); + final Transition transition = createTestTransition(TRANSIT_OPEN); ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; ArraySet<WindowContainer> participants = transition.mParticipants; @@ -83,7 +91,7 @@ public class TransitionTests extends WindowTestsBase { closing.mVisibleRequested = false; opening.mVisibleRequested = true; - int transit = TRANSIT_OLD_TASK_OPEN; + final int transit = transition.mType; int flags = 0; // Check basic both tasks participating @@ -122,7 +130,7 @@ public class TransitionTests extends WindowTestsBase { @Test public void testCreateInfo_NestedTasks() { - final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN); + final Transition transition = createTestTransition(TRANSIT_OPEN); ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; ArraySet<WindowContainer> participants = transition.mParticipants; @@ -147,7 +155,7 @@ public class TransitionTests extends WindowTestsBase { opening.mVisibleRequested = true; opening2.mVisibleRequested = true; - int transit = TRANSIT_OLD_TASK_OPEN; + final int transit = transition.mType; int flags = 0; // Check full promotion from leaf @@ -172,7 +180,7 @@ public class TransitionTests extends WindowTestsBase { @Test public void testCreateInfo_DisplayArea() { - final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN); + final Transition transition = createTestTransition(TRANSIT_OPEN); ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; ArraySet<WindowContainer> participants = transition.mParticipants; final Task showTask = createTask(mDisplayContent); @@ -194,7 +202,7 @@ public class TransitionTests extends WindowTestsBase { showing.mVisibleRequested = true; showing2.mVisibleRequested = true; - int transit = TRANSIT_OLD_TASK_OPEN; + final int transit = transition.mType; int flags = 0; // Check promotion to DisplayArea @@ -223,7 +231,7 @@ public class TransitionTests extends WindowTestsBase { @Test public void testCreateInfo_existenceChange() { - final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN); + final Transition transition = createTestTransition(TRANSIT_OPEN); final Task openTask = createTask(mDisplayContent); final ActivityRecord opening = createActivityRecord(openTask); @@ -253,7 +261,7 @@ public class TransitionTests extends WindowTestsBase { @Test public void testCreateInfo_ordering() { - final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN); + final Transition transition = createTestTransition(TRANSIT_OPEN); // pick some number with a high enough chance of being out-of-order when added to set. final int taskCount = 6; @@ -289,7 +297,7 @@ public class TransitionTests extends WindowTestsBase { @Test public void testCreateInfo_wallpaper() { - final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN); + final Transition transition = createTestTransition(TRANSIT_OPEN); // pick some number with a high enough chance of being out-of-order when added to set. final int taskCount = 4; final int showWallpaperTask = 2; @@ -339,6 +347,44 @@ public class TransitionTests extends WindowTestsBase { } @Test + public void testTargets_noIntermediatesToWallpaper() { + final Transition transition = createTestTransition(TRANSIT_OPEN); + + final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, + mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */); + // Make DA organized so we can check that they don't get included. + WindowContainer parent = wallpaperWindowToken.getParent(); + while (parent != null && parent != mDisplayContent) { + if (parent.asDisplayArea() != null) { + parent.asDisplayArea().setOrganizer( + mock(android.window.IDisplayAreaOrganizer.class), true /* skipAppear */); + } + parent = parent.getParent(); + } + final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken, + "wallpaperWindow"); + wallpaperWindowToken.setVisibleRequested(false); + transition.collect(wallpaperWindowToken); + wallpaperWindowToken.setVisibleRequested(true); + wallpaperWindow.mHasSurface = true; + doReturn(true).when(mDisplayContent).isAttached(); + transition.collect(mDisplayContent); + mDisplayContent.getWindowConfiguration().setRotation( + (mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4); + + ArraySet<WindowContainer> targets = Transition.calculateTargets( + transition.mParticipants, transition.mChanges); + TransitionInfo info = Transition.calculateTransitionInfo( + 0, 0, targets, transition.mChanges); + // The wallpaper is not organized, so it won't have a token; however, it will be marked + // as IS_WALLPAPER + assertEquals(FLAG_IS_WALLPAPER, info.getChanges().get(0).getFlags()); + // Make sure no intermediate display areas were pulled in between wallpaper and display. + assertEquals(mDisplayContent.mRemoteToken.toWindowContainerToken(), + info.getChanges().get(0).getParent()); + } + + @Test public void testIndependent() { final Transition transition = createTestTransition(TRANSIT_OPEN); ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; @@ -376,7 +422,7 @@ public class TransitionTests extends WindowTestsBase { openInOpen.mVisibleRequested = true; openInChange.mVisibleRequested = true; - int transit = TRANSIT_OLD_TASK_OPEN; + final int transit = transition.mType; int flags = 0; // Check full promotion from leaf @@ -406,6 +452,153 @@ public class TransitionTests extends WindowTestsBase { info.getChange(openInChangeTask.mRemoteToken.toWindowContainerToken()), info)); } + @Test + public void testTimeout() { + final TransitionController controller = new TransitionController(mAtm, + mock(TaskSnapshotController.class)); + final BLASTSyncEngine sync = new BLASTSyncEngine(mWm); + final CountDownLatch latch = new CountDownLatch(1); + // When the timeout is reached, it will finish the sync-group and notify transaction ready. + new Transition(TRANSIT_OPEN, 0 /* flags */, 10 /* timeoutMs */, controller, sync) { + @Override + public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) { + latch.countDown(); + } + }; + assertTrue(awaitInWmLock(() -> latch.await(3, TimeUnit.SECONDS))); + } + + @Test + public void testIntermediateVisibility() { + final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class); + final TransitionController controller = new TransitionController(mAtm, snapshotController); + final ITransitionPlayer player = new ITransitionPlayer.Default(); + controller.registerTransitionPlayer(player, null /* appThread */); + ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); + final Transition openTransition = controller.createTransition(TRANSIT_OPEN); + + // Start out with task2 visible and set up a transition that closes task2 and opens task1 + final Task task1 = createTask(mDisplayContent); + task1.mTaskOrganizer = mockOrg; + final ActivityRecord activity1 = createActivityRecord(task1); + activity1.mVisibleRequested = false; + activity1.setVisible(false); + final Task task2 = createTask(mDisplayContent); + task2.mTaskOrganizer = mockOrg; + final ActivityRecord activity2 = createActivityRecord(task1); + activity2.mVisibleRequested = true; + activity2.setVisible(true); + + openTransition.collectExistenceChange(task1); + openTransition.collectExistenceChange(activity1); + openTransition.collectExistenceChange(task2); + openTransition.collectExistenceChange(activity2); + + activity1.mVisibleRequested = true; + activity1.setVisible(true); + activity2.mVisibleRequested = false; + + // Using abort to force-finish the sync (since we can't wait for drawing in unit test). + // We didn't call abort on the transition itself, so it will still run onTransactionReady + // normally. + mWm.mSyncEngine.abort(openTransition.getSyncId()); + + // Before finishing openTransition, we are now going to simulate closing task1 to return + // back to (open) task2. + final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE); + + closeTransition.collectExistenceChange(task1); + closeTransition.collectExistenceChange(activity1); + closeTransition.collectExistenceChange(task2); + closeTransition.collectExistenceChange(activity2); + + activity1.mVisibleRequested = false; + activity2.mVisibleRequested = true; + + openTransition.finishTransition(); + + // We finished the openTransition. Even though activity1 is visibleRequested=false, since + // the closeTransition animation hasn't played yet, make sure that we didn't commit + // visible=false on activity1 since it needs to remain visible for the animation. + assertTrue(activity1.isVisible()); + assertTrue(activity2.isVisible()); + + // Using abort to force-finish the sync (since we obviously can't wait for drawing). + // We didn't call abort on the actual transition, so it will still run onTransactionReady + // normally. + mWm.mSyncEngine.abort(closeTransition.getSyncId()); + + closeTransition.finishTransition(); + + assertFalse(activity1.isVisible()); + assertTrue(activity2.isVisible()); + } + + @Test + public void testTransientLaunch() { + final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class); + final TransitionController controller = new TransitionController(mAtm, snapshotController); + final ITransitionPlayer player = new ITransitionPlayer.Default(); + controller.registerTransitionPlayer(player, null /* appThread */); + ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); + final Transition openTransition = controller.createTransition(TRANSIT_OPEN); + + // Start out with task2 visible and set up a transition that closes task2 and opens task1 + final Task task1 = createTask(mDisplayContent); + task1.mTaskOrganizer = mockOrg; + final ActivityRecord activity1 = createActivityRecord(task1); + activity1.mVisibleRequested = false; + activity1.setVisible(false); + final Task task2 = createTask(mDisplayContent); + task2.mTaskOrganizer = mockOrg; + final ActivityRecord activity2 = createActivityRecord(task2); + activity2.mVisibleRequested = true; + activity2.setVisible(true); + + openTransition.collectExistenceChange(task1); + openTransition.collectExistenceChange(activity1); + openTransition.collectExistenceChange(task2); + openTransition.collectExistenceChange(activity2); + + activity1.mVisibleRequested = true; + activity1.setVisible(true); + activity2.mVisibleRequested = false; + + // Using abort to force-finish the sync (since we can't wait for drawing in unit test). + // We didn't call abort on the transition itself, so it will still run onTransactionReady + // normally. + mWm.mSyncEngine.abort(openTransition.getSyncId()); + + verify(snapshotController, times(1)).recordTaskSnapshot(eq(task2), eq(false)); + + openTransition.finishTransition(); + + // We are now going to simulate closing task1 to return back to (open) task2. + final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE); + + closeTransition.collectExistenceChange(task1); + closeTransition.collectExistenceChange(activity1); + closeTransition.collectExistenceChange(task2); + closeTransition.collectExistenceChange(activity2); + closeTransition.setTransientLaunch(activity2); + + activity1.mVisibleRequested = false; + activity2.mVisibleRequested = true; + + // Using abort to force-finish the sync (since we obviously can't wait for drawing). + // We didn't call abort on the actual transition, so it will still run onTransactionReady + // normally. + mWm.mSyncEngine.abort(closeTransition.getSyncId()); + + // Make sure we haven't called recordSnapshot (since we are transient, it shouldn't be + // called until finish). + verify(snapshotController, times(0)).recordTaskSnapshot(eq(task1), eq(false)); + + closeTransition.finishTransition(); + + verify(snapshotController, times(1)).recordTaskSnapshot(eq(task1), eq(false)); + } + /** Fill the change map with all the parents of top. Change maps are usually fully populated */ private static void fillChangeMap(ArrayMap<WindowContainer, Transition.ChangeInfo> changes, WindowContainer top) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index 3f0c13c83816..f366f57bae08 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -316,7 +316,8 @@ public class WallpaperControllerTests extends WindowTestsBase { final IBinder mockBinder = mock(IBinder.class); final ITransitionPlayer mockPlayer = mock(ITransitionPlayer.class); doReturn(mockBinder).when(mockPlayer).asBinder(); - mWm.mAtmService.getTransitionController().registerTransitionPlayer(mockPlayer); + mWm.mAtmService.getTransitionController().registerTransitionPlayer(mockPlayer, + null /* appThread */); Transition transit = mWm.mAtmService.getTransitionController().createTransition(TRANSIT_OPEN); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java index e970c2a2f2ed..e2f1334c7f8c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java @@ -19,7 +19,6 @@ package com.android.server.wm; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM; -import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE; import static org.mockito.ArgumentMatchers.any; @@ -89,43 +88,6 @@ public class WindowAnimationSpecTest { } @Test - public void testApply_clipBeforeNoAnimationBounds() { - // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 0, 0) - WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null, - mStackBounds, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM, - true /* isAppAnimation */, 0 /* windowCornerRadius */); - windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); - verify(mTransaction).setWindowCrop(eq(mSurfaceControl), - argThat(rect -> rect.equals(mStackBounds))); - } - - @Test - public void testApply_clipBeforeNoStackBounds() { - // Stack bounds is (0, 0, 0, 0) animation clip is (0, 0, 20, 20) - Rect windowCrop = new Rect(0, 0, 20, 20); - Animation a = createClipRectAnimation(windowCrop, windowCrop); - a.initialize(0, 0, 0, 0); - WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, - null, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM, - true /* isAppAnimation */, 0 /* windowCornerRadius */); - windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); - verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty)); - } - - @Test - public void testApply_setCornerRadius() { - final float windowCornerRadius = 30f; - WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null, - mStackBounds, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM, - true /* isAppAnimation */, windowCornerRadius); - windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); - verify(mTransaction, never()).setCornerRadius(eq(mSurfaceControl), eq(windowCornerRadius)); - when(mAnimation.hasRoundedCorners()).thenReturn(true); - windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); - verify(mTransaction).setCornerRadius(eq(mSurfaceControl), eq(windowCornerRadius)); - } - - @Test public void testApply_setCornerRadius_noClip() { final float windowCornerRadius = 30f; WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null, @@ -136,32 +98,6 @@ public class WindowAnimationSpecTest { verify(mTransaction, never()).setCornerRadius(any(), anyFloat()); } - @Test - public void testApply_clipBeforeSmallerAnimationClip() { - // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 5, 5) - Rect windowCrop = new Rect(0, 0, 5, 5); - Animation a = createClipRectAnimation(windowCrop, windowCrop); - WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, - mStackBounds, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM, - true /* isAppAnimation */, 0 /* windowCornerRadius */); - windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); - verify(mTransaction).setWindowCrop(eq(mSurfaceControl), - argThat(rect -> rect.equals(windowCrop))); - } - - @Test - public void testApply_clipBeforeSmallerStackClip() { - // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 20, 20) - Rect windowCrop = new Rect(0, 0, 20, 20); - Animation a = createClipRectAnimation(windowCrop, windowCrop); - WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, - mStackBounds, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM, - true /* isAppAnimation */, 0 /* windowCornerRadius */); - windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); - verify(mTransaction).setWindowCrop(eq(mSurfaceControl), - argThat(rect -> rect.equals(mStackBounds))); - } - private Animation createClipRectAnimation(Rect fromClip, Rect toClip) { Animation a = new ClipRectAnimation(fromClip, toClip); a.initialize(0, 0, 0, 0); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 00f3d8b874f7..6ae3f9447ee2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -25,6 +25,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE; import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; @@ -52,6 +53,7 @@ import static com.android.server.wm.WindowContainer.POSITION_TOP; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -1071,6 +1073,163 @@ public class WindowContainerTests extends WindowTestsBase { verify(surfaceAnimator).setRelativeLayer(t, relativeParent, 1 /* layer */); } + @Test + public void testAssignAnimationLayer() { + final WindowContainer container = new WindowContainer(mWm); + container.mSurfaceControl = mock(SurfaceControl.class); + final SurfaceAnimator surfaceAnimator = container.mSurfaceAnimator; + final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer; + final SurfaceControl relativeParent = mock(SurfaceControl.class); + final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + spyOn(container); + spyOn(surfaceAnimator); + spyOn(surfaceFreezer); + + container.setLayer(t, 1); + container.setRelativeLayer(t, relativeParent, 2); + + // Set through surfaceAnimator if surfaceFreezer doesn't have leash. + verify(surfaceAnimator).setLayer(t, 1); + verify(surfaceAnimator).setRelativeLayer(t, relativeParent, 2); + verify(surfaceFreezer, never()).setLayer(any(), anyInt()); + verify(surfaceFreezer, never()).setRelativeLayer(any(), any(), anyInt()); + + clearInvocations(surfaceAnimator); + clearInvocations(surfaceFreezer); + doReturn(true).when(surfaceFreezer).hasLeash(); + + container.setLayer(t, 1); + container.setRelativeLayer(t, relativeParent, 2); + + // Set through surfaceFreezer if surfaceFreezer has leash. + verify(surfaceFreezer).setLayer(t, 1); + verify(surfaceFreezer).setRelativeLayer(t, relativeParent, 2); + verify(surfaceAnimator, never()).setLayer(any(), anyInt()); + verify(surfaceAnimator, never()).setRelativeLayer(any(), any(), anyInt()); + } + + @Test + public void testStartChangeTransitionWhenPreviousIsNotFinished() { + final WindowContainer container = createTaskFragmentWithParentTask( + createTask(mDisplayContent), false); + container.mSurfaceControl = mock(SurfaceControl.class); + final SurfaceAnimator surfaceAnimator = container.mSurfaceAnimator; + final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer; + final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + spyOn(container); + spyOn(surfaceAnimator); + mockSurfaceFreezerSnapshot(surfaceFreezer); + doReturn(t).when(container).getPendingTransaction(); + doReturn(t).when(container).getSyncTransaction(); + + // Leash and snapshot created for change transition. + container.initializeChangeTransition(new Rect(0, 0, 1000, 2000)); + + assertNotNull(surfaceFreezer.mLeash); + assertNotNull(surfaceFreezer.mSnapshot); + assertEquals(surfaceFreezer.mLeash, container.getAnimationLeash()); + + // Start animation: surfaceAnimator take over the leash and snapshot from surfaceFreezer. + container.applyAnimationUnchecked(null /* lp */, true /* enter */, + TRANSIT_OLD_TASK_FRAGMENT_CHANGE, false /* isVoiceInteraction */, + null /* sources */); + + assertNull(surfaceFreezer.mLeash); + assertNull(surfaceFreezer.mSnapshot); + assertNotNull(surfaceAnimator.mLeash); + assertNotNull(surfaceAnimator.mSnapshot); + final SurfaceControl prevLeash = surfaceAnimator.mLeash; + final SurfaceFreezer.Snapshot prevSnapshot = surfaceAnimator.mSnapshot; + + // Prepare another change transition. + container.initializeChangeTransition(new Rect(0, 0, 1000, 2000)); + + assertNotNull(surfaceFreezer.mLeash); + assertNotNull(surfaceFreezer.mSnapshot); + assertEquals(surfaceFreezer.mLeash, container.getAnimationLeash()); + assertNotEquals(prevLeash, container.getAnimationLeash()); + + // Start another animation before the previous one is finished, it should reset the previous + // one, but not change the current one. + container.applyAnimationUnchecked(null /* lp */, true /* enter */, + TRANSIT_OLD_TASK_FRAGMENT_CHANGE, false /* isVoiceInteraction */, + null /* sources */); + + verify(container, never()).onAnimationLeashLost(any()); + verify(surfaceFreezer, never()).unfreeze(any()); + assertNotNull(surfaceAnimator.mLeash); + assertNotNull(surfaceAnimator.mSnapshot); + assertEquals(surfaceAnimator.mLeash, container.getAnimationLeash()); + assertNotEquals(prevLeash, surfaceAnimator.mLeash); + assertNotEquals(prevSnapshot, surfaceAnimator.mSnapshot); + + // Clean up after animation finished. + surfaceAnimator.mInnerAnimationFinishedCallback.onAnimationFinished( + ANIMATION_TYPE_APP_TRANSITION, surfaceAnimator.getAnimation()); + + verify(container).onAnimationLeashLost(any()); + assertNull(surfaceAnimator.mLeash); + assertNull(surfaceAnimator.mSnapshot); + } + + @Test + public void testUnfreezeWindow_removeWindowFromChanging() { + final WindowContainer container = createTaskFragmentWithParentTask( + createTask(mDisplayContent), false); + mockSurfaceFreezerSnapshot(container.mSurfaceFreezer); + final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + + container.initializeChangeTransition(new Rect(0, 0, 1000, 2000)); + + assertTrue(mDisplayContent.mChangingContainers.contains(container)); + + container.mSurfaceFreezer.unfreeze(t); + + assertFalse(mDisplayContent.mChangingContainers.contains(container)); + } + + @Test + public void testFailToTaskSnapshot_unfreezeWindow() { + final WindowContainer container = createTaskFragmentWithParentTask( + createTask(mDisplayContent), false); + final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + spyOn(container.mSurfaceFreezer); + + container.initializeChangeTransition(new Rect(0, 0, 1000, 2000)); + + verify(container.mSurfaceFreezer).freeze(any(), any(), any(), any()); + verify(container.mSurfaceFreezer).unfreeze(any()); + assertTrue(mDisplayContent.mChangingContainers.isEmpty()); + } + + @Test + public void testRemoveUnstartedFreezeSurfaceWhenFreezeAgain() { + final WindowContainer container = createTaskFragmentWithParentTask( + createTask(mDisplayContent), false); + container.mSurfaceControl = mock(SurfaceControl.class); + final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer; + mockSurfaceFreezerSnapshot(surfaceFreezer); + final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + spyOn(container); + doReturn(t).when(container).getPendingTransaction(); + doReturn(t).when(container).getSyncTransaction(); + + // Leash and snapshot created for change transition. + container.initializeChangeTransition(new Rect(0, 0, 1000, 2000)); + + assertNotNull(surfaceFreezer.mLeash); + assertNotNull(surfaceFreezer.mSnapshot); + + final SurfaceControl prevLeash = surfaceFreezer.mLeash; + final SurfaceFreezer.Snapshot prevSnapshot = surfaceFreezer.mSnapshot; + spyOn(prevSnapshot); + + container.initializeChangeTransition(new Rect(0, 0, 1500, 2500)); + + verify(t).remove(prevLeash); + verify(prevSnapshot).destroy(t); + } + /* Used so we can gain access to some protected members of the {@link WindowContainer} class */ private static class TestWindowContainer extends WindowContainer<TestWindowContainer> { private final int mLayer; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java index e5eba57f223d..646647fcc4ca 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java @@ -17,22 +17,35 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.STATE_OFF; +import static android.view.Display.STATE_ON; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.window.WindowProvider.KEY_IS_WINDOW_PROVIDER_SERVICE; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import android.app.IWindowToken; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.view.Display; +import android.view.DisplayInfo; import androidx.test.filters.SmallTest; @@ -55,12 +68,15 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { private static final int ANOTHER_UID = 1000; private final IBinder mClientToken = new Binder(); - private WindowContainer mContainer; + private WindowContainer<?> mContainer; @Before public void setUp() { mController = new WindowContextListenerController(); mContainer = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent); + // Make display on to verify configuration propagation. + mDefaultDisplay.getDisplayInfo().state = STATE_ON; + mDisplayContent.getDisplayInfo().state = STATE_ON; } @Test @@ -76,7 +92,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertEquals(2, mController.mListeners.size()); - final WindowContainer container = createTestWindowToken(TYPE_APPLICATION_OVERLAY, + final WindowContainer<?> container = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDefaultDisplay); mController.registerWindowContainerListener(mClientToken, container, -1, TYPE_APPLICATION_OVERLAY, null /* options */); @@ -89,6 +105,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertEquals(container, listener.getWindowContainer()); } + @UseTestDisplay @Test public void testRegisterWindowContextListenerClientConfigPropagation() { final TestWindowTokenClient clientToken = new TestWindowTokenClient(); @@ -107,7 +124,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertEquals(mDisplayContent.mDisplayId, clientToken.mDisplayId); // Update the WindowContainer. - final WindowContainer container = createTestWindowToken(TYPE_APPLICATION_OVERLAY, + final WindowContainer<?> container = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDefaultDisplay); final Configuration config2 = container.getConfiguration(); final Rect bounds2 = new Rect(0, 0, 20, 20); @@ -174,7 +191,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { .setDisplayContent(mDefaultDisplay) .setFromClientToken(true) .build(); - final DisplayArea da = windowContextCreatedToken.getDisplayArea(); + final DisplayArea<?> da = windowContextCreatedToken.getDisplayArea(); mController.registerWindowContainerListener(mClientToken, windowContextCreatedToken, TEST_UID, TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, null /* options */); @@ -192,11 +209,12 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { // Let the Display to be created with the DualDisplay policy. final DisplayAreaPolicy.Provider policyProvider = new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(); - Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); + doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); // Create a DisplayContent with dual RootDisplayArea DualDisplayAreaGroupPolicyTest.DualDisplayContent dualDisplayContent = new DualDisplayAreaGroupPolicyTest.DualDisplayContent .Builder(mAtm, 1000, 1000).build(); + dualDisplayContent.getDisplayInfo().state = STATE_ON; final DisplayArea.Tokens imeContainer = dualDisplayContent.getImeContainer(); // Put the ImeContainer to the first sub-RootDisplayArea dualDisplayContent.mFirstRoot.placeImeContainer(imeContainer); @@ -222,7 +240,62 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertThat(mController.getContainer(mClientToken)).isEqualTo(imeContainer); } - private class TestWindowTokenClient extends IWindowToken.Stub { + @Test + public void testConfigUpdateForSuspendedWindowContext() { + final TestWindowTokenClient mockToken = new TestWindowTokenClient(); + spyOn(mockToken); + + mContainer.getDisplayContent().getDisplayInfo().state = STATE_OFF; + + final Configuration config1 = mContainer.getConfiguration(); + final Rect bounds1 = new Rect(0, 0, 10, 10); + config1.windowConfiguration.setBounds(bounds1); + config1.densityDpi = 100; + mContainer.onRequestedOverrideConfigurationChanged(config1); + + mController.registerWindowContainerListener(mockToken, mContainer, -1, + TYPE_APPLICATION_OVERLAY, null /* options */); + + verify(mockToken, never()).onConfigurationChanged(any(), anyInt()); + + // Turn on the display and verify if the client receive the callback + Display display = mContainer.getDisplayContent().getDisplay(); + spyOn(display); + Mockito.doAnswer(invocation -> { + final DisplayInfo info = mContainer.getDisplayContent().getDisplayInfo(); + info.state = STATE_ON; + ((DisplayInfo) invocation.getArgument(0)).copyFrom(info); + return null; + }).when(display).getDisplayInfo(any(DisplayInfo.class)); + + mContainer.getDisplayContent().onDisplayChanged(); + + assertThat(mockToken.mConfiguration).isEqualTo(config1); + assertThat(mockToken.mDisplayId).isEqualTo(mContainer.getDisplayContent().getDisplayId()); + } + + @Test + public void testReportConfigUpdateForSuspendedWindowProviderService() { + final TestWindowTokenClient clientToken = new TestWindowTokenClient(); + final Bundle options = new Bundle(); + options.putBoolean(KEY_IS_WINDOW_PROVIDER_SERVICE, true); + + mContainer.getDisplayContent().getDisplayInfo().state = STATE_OFF; + + final Configuration config1 = mContainer.getConfiguration(); + final Rect bounds1 = new Rect(0, 0, 10, 10); + config1.windowConfiguration.setBounds(bounds1); + config1.densityDpi = 100; + mContainer.onRequestedOverrideConfigurationChanged(config1); + + mController.registerWindowContainerListener(clientToken, mContainer, -1, + TYPE_APPLICATION_OVERLAY, options); + + assertThat(clientToken.mConfiguration).isEqualTo(config1); + assertThat(clientToken.mDisplayId).isEqualTo(mDisplayContent.mDisplayId); + } + + private static class TestWindowTokenClient extends IWindowToken.Stub { private Configuration mConfiguration; private int mDisplayId; private boolean mRemoved; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java index a1f89ec75784..316309c8440e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java @@ -35,6 +35,7 @@ import android.view.DisplayInfo; import android.view.Gravity; import android.view.InsetsSource; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.WindowManager; import androidx.test.filters.FlakyTest; @@ -57,12 +58,14 @@ import org.mockito.Mockito; public class WindowFrameTests extends WindowTestsBase { private DisplayContent mTestDisplayContent; + private DisplayFrames mTestDisplayFrames; @Before public void setUp() throws Exception { DisplayInfo testDisplayInfo = new DisplayInfo(mDisplayInfo); testDisplayInfo.displayCutout = null; mTestDisplayContent = createNewDisplay(testDisplayInfo); + mTestDisplayFrames = mTestDisplayContent.mDisplayFrames; } // Do not use this function directly in the tests below. Instead, use more explicit function @@ -99,7 +102,7 @@ public class WindowFrameTests extends WindowTestsBase { // Here the window has FILL_PARENT, FILL_PARENT // so we expect it to fill the entire available frame. w.getWindowFrames().setFrames(pf, pf); - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); assertFrame(w, 0, 0, 1000, 1000); assertRelFrame(w, 0, 0, 1000, 1000); @@ -108,14 +111,14 @@ public class WindowFrameTests extends WindowTestsBase { // and we use mRequestedWidth/mRequestedHeight w.mAttrs.width = 300; w.mAttrs.height = 300; - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); // Explicit width and height without requested width/height // gets us nothing. assertFrame(w, 0, 0, 0, 0); w.mRequestedWidth = 300; w.mRequestedHeight = 300; - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); // With requestedWidth/Height we can freely choose our size within the // parent bounds. assertFrame(w, 0, 0, 300, 300); @@ -128,14 +131,14 @@ public class WindowFrameTests extends WindowTestsBase { w.mRequestedWidth = -1; w.mAttrs.width = 100; w.mAttrs.height = 100; - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); assertFrame(w, 0, 0, 100, 100); w.mAttrs.flags = 0; // But sizes too large will be clipped to the containing frame w.mRequestedWidth = 1200; w.mRequestedHeight = 1200; - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); assertFrame(w, 0, 0, 1000, 1000); // Before they are clipped though windows will be shifted @@ -143,7 +146,7 @@ public class WindowFrameTests extends WindowTestsBase { w.mAttrs.y = 300; w.mRequestedWidth = 1000; w.mRequestedHeight = 1000; - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); assertFrame(w, 0, 0, 1000, 1000); // If there is room to move around in the parent frame the window will be shifted according @@ -153,18 +156,18 @@ public class WindowFrameTests extends WindowTestsBase { w.mRequestedWidth = 300; w.mRequestedHeight = 300; w.mAttrs.gravity = Gravity.RIGHT | Gravity.TOP; - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); assertFrame(w, 700, 0, 1000, 300); assertRelFrame(w, 700, 0, 1000, 300); w.mAttrs.gravity = Gravity.RIGHT | Gravity.BOTTOM; - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); assertFrame(w, 700, 700, 1000, 1000); assertRelFrame(w, 700, 700, 1000, 1000); // Window specified x and y are interpreted as offsets in the opposite // direction of gravity w.mAttrs.x = 100; w.mAttrs.y = 100; - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); assertFrame(w, 600, 600, 900, 900); assertRelFrame(w, 600, 600, 900, 900); } @@ -191,7 +194,7 @@ public class WindowFrameTests extends WindowTestsBase { final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight); final WindowFrames windowFrames = w.getWindowFrames(); windowFrames.setFrames(pf, pf); - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); // For non fullscreen tasks the containing frame is based off the // task bounds not the parent frame. assertEquals(resolvedTaskBounds, w.getFrame()); @@ -204,7 +207,7 @@ public class WindowFrameTests extends WindowTestsBase { final int cfBottom = logicalHeight / 2; final Rect cf = new Rect(0, 0, cfRight, cfBottom); windowFrames.setFrames(pf, pf); - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); assertEquals(resolvedTaskBounds, w.getFrame()); assertEquals(0, w.getRelativeFrame().left); assertEquals(0, w.getRelativeFrame().top); @@ -233,7 +236,7 @@ public class WindowFrameTests extends WindowTestsBase { final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight); final WindowFrames windowFrames = w.getWindowFrames(); windowFrames.setFrames(pf, pf); - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); // For non fullscreen tasks the containing frame is based off the // task bounds not the parent frame. assertFrame(w, taskLeft, taskTop, taskRight, taskBottom); @@ -249,7 +252,7 @@ public class WindowFrameTests extends WindowTestsBase { task.setWindowingMode(WINDOWING_MODE_FULLSCREEN); task.setBounds(null); windowFrames.setFrames(pf, pf); - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); assertFrame(w, cf); } @@ -274,7 +277,9 @@ public class WindowFrameTests extends WindowTestsBase { imeFrame.top = 400; imeSource.setFrame(imeFrame); imeSource.setVisible(true); - w.updateRequestedVisibility(state); + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_IME, true); + w.setRequestedVisibilities(requestedVisibilities); w.mAboveInsetsState.addSource(imeSource); // With no insets or system decor all the frames incoming from PhoneWindowManager @@ -285,7 +290,7 @@ public class WindowFrameTests extends WindowTestsBase { final Rect winRect = new Rect(200, 200, 300, 500); task.setBounds(winRect); w.getWindowFrames().setFrames(pf, pf); - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); assertFrame(w, winRect.left, imeFrame.top - winRect.height(), winRect.right, imeFrame.top); // Now check that it won't get moved beyond the top @@ -293,7 +298,7 @@ public class WindowFrameTests extends WindowTestsBase { task.setBounds(winRect); w.setBounds(winRect); w.getWindowFrames().setFrames(pf, pf); - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); assertFrame(w, winRect.left, 0, winRect.right, winRect.height()); // Now we have status bar. Check that it won't go into the status bar area. @@ -301,14 +306,14 @@ public class WindowFrameTests extends WindowTestsBase { statusBarFrame.bottom = 60; state.getSource(ITYPE_STATUS_BAR).setFrame(statusBarFrame); w.getWindowFrames().setFrames(pf, pf); - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); assertFrame(w, winRect.left, statusBarFrame.bottom, winRect.right, statusBarFrame.bottom + winRect.height()); // Check that it's moved back without ime insets state.removeSource(ITYPE_IME); w.getWindowFrames().setFrames(pf, pf); - w.computeFrame(); + w.computeFrame(mTestDisplayFrames); assertEquals(winRect, w.getFrame()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index d9aa871447be..a91298f73d08 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -56,6 +56,7 @@ import android.platform.test.annotations.Presubmit; import android.view.IWindowSessionCallback; import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.View; import android.view.WindowManager; @@ -107,9 +108,9 @@ public class WindowManagerServiceTests extends WindowTestsBase { Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */); spyOn(mWm.mAtmService); - mWm.handleTaskFocusChange(tappedTask); + mWm.handleTaskFocusChange(tappedTask, null /* window */); - verify(mWm.mAtmService).setFocusedTask(tappedTask.mTaskId); + verify(mWm.mAtmService).setFocusedTask(tappedTask.mTaskId, null); } @Test @@ -128,9 +129,9 @@ public class WindowManagerServiceTests extends WindowTestsBase { Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */); spyOn(mWm.mAtmService); - mWm.handleTaskFocusChange(tappedTask); + mWm.handleTaskFocusChange(tappedTask, null /* window */); - verify(mWm.mAtmService, never()).setFocusedTask(tappedTask.mTaskId); + verify(mWm.mAtmService, never()).setFocusedTask(tappedTask.mTaskId, null); } @Test @@ -151,9 +152,9 @@ public class WindowManagerServiceTests extends WindowTestsBase { Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */); spyOn(mWm.mAtmService); - mWm.handleTaskFocusChange(tappedTask); + mWm.handleTaskFocusChange(tappedTask, null /* window */); - verify(mWm.mAtmService).setFocusedTask(tappedTask.mTaskId); + verify(mWm.mAtmService).setFocusedTask(tappedTask.mTaskId, null); } @Test @@ -278,7 +279,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { .getWindowType(eq(windowContextToken)); mWm.addWindow(session, new TestIWindow(), params, View.VISIBLE, DEFAULT_DISPLAY, - UserHandle.USER_SYSTEM, new InsetsState(), null, new InsetsState(), + UserHandle.USER_SYSTEM, new InsetsVisibilities(), null, new InsetsState(), new InsetsSourceControl[0]); verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(), diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index ab496cf34acc..75a87ba9e04d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -42,8 +42,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; -import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS; -import static com.android.server.wm.Task.ActivityState.RESUMED; +import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowContainer.SYNC_STATE_READY; @@ -62,6 +61,7 @@ import static org.mockito.Mockito.clearInvocations; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityOptions; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.IRequestFinishCallback; import android.app.PictureInPictureParams; @@ -70,7 +70,6 @@ import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; -import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; @@ -81,7 +80,9 @@ import android.view.SurfaceControl; import android.window.ITaskOrganizer; import android.window.IWindowContainerTransactionCallback; import android.window.StartingWindowInfo; +import android.window.StartingWindowRemovalInfo; import android.window.TaskAppearedInfo; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; @@ -346,7 +347,7 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testDisplayAreaTransaction() { removeGlobalMinSizeRestriction(); - final DisplayArea displayArea = new DisplayArea<>(mWm, ABOVE_TASKS, "DisplayArea"); + final DisplayArea displayArea = mDisplayContent.getDefaultTaskDisplayArea(); testTransaction(displayArea); } @@ -364,7 +365,7 @@ public class WindowOrganizerTests extends WindowTestsBase { .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); testSetWindowingMode(rootTask); - final DisplayArea displayArea = new DisplayArea<>(mWm, ABOVE_TASKS, "DisplayArea"); + final DisplayArea displayArea = mDisplayContent.getDefaultTaskDisplayArea(); displayArea.setWindowingMode(WINDOWING_MODE_FREEFORM); testSetWindowingMode(displayArea); } @@ -542,6 +543,36 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Test + public void testSetAdjacentLaunchRoot() { + DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY); + + final Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + dc, WINDOWING_MODE_MULTI_WINDOW, null); + final RunningTaskInfo info1 = task1.getTaskInfo(); + final Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + dc, WINDOWING_MODE_MULTI_WINDOW, null); + final RunningTaskInfo info2 = task2.getTaskInfo(); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setAdjacentRoots(info1.token, info2.token, false /* moveTogether */); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); + assertEquals(task1.getAdjacentTaskFragment(), task2); + assertEquals(task2.getAdjacentTaskFragment(), task1); + + wct = new WindowContainerTransaction(); + wct.setLaunchAdjacentFlagRoot(info1.token); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); + assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, task1); + + task1.setAdjacentTaskFragment(null, false /* moveTogether */); + task2.setAdjacentTaskFragment(null, false /* moveTogether */); + wct = new WindowContainerTransaction(); + wct.clearLaunchAdjacentFlagRoot(info1.token); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); + assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, null); + } + + @Test public void testTileAddRemoveChild() { final StubOrganizer listener = new StubOrganizer(); mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener); @@ -779,8 +810,7 @@ public class WindowOrganizerTests extends WindowTestsBase { @Override public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { } @Override - public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, - boolean playRevealAnimation) { } + public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { } @Override public void copySplashScreenView(int taskId) { } @Override @@ -1259,21 +1289,42 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testStartTasksInTransaction() { WindowContainerTransaction wct = new WindowContainerTransaction(); - Bundle testOptions = new Bundle(); - testOptions.putInt("test", 20); + ActivityOptions testOptions = ActivityOptions.makeBasic(); + testOptions.setTransientLaunch(); wct.startTask(1, null /* options */); - wct.startTask(2, testOptions); - spyOn(mWm.mAtmService); - doReturn(START_CANCELED).when(mWm.mAtmService).startActivityFromRecents(anyInt(), any()); + wct.startTask(2, testOptions.toBundle()); + spyOn(mWm.mAtmService.mTaskSupervisor); + doReturn(START_CANCELED).when(mWm.mAtmService.mTaskSupervisor).startActivityFromRecents( + anyInt(), anyInt(), anyInt(), any()); clearInvocations(mWm.mAtmService); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); - final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); - verify(mWm.mAtmService, times(1)).startActivityFromRecents(eq(1), bundleCaptor.capture()); - assertTrue(bundleCaptor.getValue().isEmpty()); + verify(mWm.mAtmService.mTaskSupervisor, times(1)).startActivityFromRecents( + anyInt(), anyInt(), eq(1), any()); + + final ArgumentCaptor<SafeActivityOptions> optionsCaptor = + ArgumentCaptor.forClass(SafeActivityOptions.class); + verify(mWm.mAtmService.mTaskSupervisor, times(1)).startActivityFromRecents( + anyInt(), anyInt(), eq(2), optionsCaptor.capture()); + assertTrue(optionsCaptor.getValue().getOriginalOptions().getTransientLaunch()); + } + + @Test + public void testResumeTopsWhenLeavingPinned() { + final ActivityRecord record = makePipableActivity(); + final Task rootTask = record.getRootTask(); + + clearInvocations(mWm.mAtmService.mRootWindowContainer); + final WindowContainerTransaction t = new WindowContainerTransaction(); + WindowContainerToken wct = rootTask.mRemoteToken.toWindowContainerToken(); + t.setWindowingMode(wct, WINDOWING_MODE_PINNED); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); + verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities(); - verify(mWm.mAtmService, times(1)).startActivityFromRecents(eq(2), bundleCaptor.capture()); - assertEquals(20, bundleCaptor.getValue().getInt("test")); + clearInvocations(mWm.mAtmService.mRootWindowContainer); + t.setWindowingMode(wct, WINDOWING_MODE_FULLSCREEN); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); + verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities(); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java index ed18d26f8448..c56b6141a652 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -23,11 +23,18 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.ActivityRecord.State.PAUSED; +import static com.android.server.wm.ActivityRecord.State.PAUSING; +import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.ActivityRecord.State.STARTED; +import static com.android.server.wm.ActivityRecord.State.STOPPED; +import static com.android.server.wm.ActivityRecord.State.STOPPING; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -43,6 +50,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.ServiceInfo; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.LocaleList; import android.platform.test.annotations.Presubmit; import org.junit.Before; @@ -311,17 +319,17 @@ public class WindowProcessControllerTests extends WindowTestsBase { callbackResult[0] = 0; activity.mVisibleRequested = false; - activity.setState(Task.ActivityState.PAUSED, "test"); + activity.setState(PAUSED, "test"); mWpc.computeOomAdjFromActivities(callback); assertEquals(paused, callbackResult[0]); callbackResult[0] = 0; - activity.setState(Task.ActivityState.STOPPING, "test"); + activity.setState(STOPPING, "test"); mWpc.computeOomAdjFromActivities(callback); assertEquals(stopping, callbackResult[0]); callbackResult[0] = 0; - activity.setState(Task.ActivityState.STOPPED, "test"); + activity.setState(STOPPED, "test"); mWpc.computeOomAdjFromActivities(callback); assertEquals(other, callbackResult[0]); } @@ -332,25 +340,25 @@ public class WindowProcessControllerTests extends WindowTestsBase { spyOn(tracker); final ActivityRecord activity = createActivityRecord(mWpc); activity.mVisibleRequested = true; - activity.setState(Task.ActivityState.STARTED, "test"); + activity.setState(STARTED, "test"); verify(tracker).onAnyActivityVisible(mWpc); assertTrue(mWpc.hasVisibleActivities()); - activity.setState(Task.ActivityState.RESUMED, "test"); + activity.setState(RESUMED, "test"); verify(tracker).onActivityResumedWhileVisible(mWpc); assertTrue(tracker.hasResumedActivity(mWpc.mUid)); activity.makeFinishingLocked(); - activity.setState(Task.ActivityState.PAUSING, "test"); + activity.setState(PAUSING, "test"); assertFalse(tracker.hasResumedActivity(mWpc.mUid)); assertTrue(mWpc.hasForegroundActivities()); activity.setVisibility(false); activity.mVisibleRequested = false; - activity.setState(Task.ActivityState.STOPPED, "test"); + activity.setState(STOPPED, "test"); verify(tracker).onAllActivitiesInvisible(mWpc); assertFalse(mWpc.hasVisibleActivities()); @@ -365,8 +373,9 @@ public class WindowProcessControllerTests extends WindowTestsBase { public void testTopActivityUiModeChangeScheduleConfigChange() { final ActivityRecord activity = createActivityRecord(mWpc); activity.mVisibleRequested = true; - doReturn(true).when(activity).setOverrideNightMode(anyInt()); - mWpc.updateNightModeForAllActivities(Configuration.UI_MODE_NIGHT_YES); + doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any()); + mWpc.updateAppSpecificSettingsForAllActivities(Configuration.UI_MODE_NIGHT_YES, + LocaleList.forLanguageTags("en-XA")); verify(activity).ensureActivityConfiguration(anyInt(), anyBoolean()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index d88ac256be5c..e6ad68aafaec 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.view.InsetsState.ITYPE_IME; @@ -85,6 +86,7 @@ import android.view.Gravity; import android.view.InputWindowHandle; import android.view.InsetsSource; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.SurfaceControl; import android.view.WindowManager; @@ -437,9 +439,9 @@ public class WindowStateTests extends WindowTestsBase { .setWindow(statusBar, null /* frameProvider */, null /* imeFrameProvider */); mDisplayContent.getInsetsStateController().onBarControlTargetChanged( app, null /* fakeTopControlling */, app, null /* fakeNavControlling */); - final InsetsState state = new InsetsState(); - state.getSource(ITYPE_STATUS_BAR).setVisible(false); - app.updateRequestedVisibility(state); + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false); + app.setRequestedVisibilities(requestedVisibilities); mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR) .updateClientVisibility(app); waitUntilHandlersIdle(); @@ -834,8 +836,7 @@ public class WindowStateTests extends WindowTestsBase { WindowState sameTokenWindow = createWindow(null, TYPE_BASE_APPLICATION, mAppWindow.mToken, "SameTokenWindow"); mDisplayContent.setImeLayeringTarget(mAppWindow); - sameTokenWindow.mActivityRecord.getRootTask().setWindowingMode( - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + sameTokenWindow.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); assertTrue(sameTokenWindow.needsRelativeLayeringToIme()); sameTokenWindow.removeImmediately(); assertFalse(sameTokenWindow.needsRelativeLayeringToIme()); @@ -847,8 +848,7 @@ public class WindowStateTests extends WindowTestsBase { WindowState sameTokenWindow = createWindow(null, TYPE_APPLICATION_STARTING, mAppWindow.mToken, "SameTokenWindow"); mDisplayContent.setImeLayeringTarget(mAppWindow); - sameTokenWindow.mActivityRecord.getRootTask().setWindowingMode( - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + sameTokenWindow.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); assertFalse(sameTokenWindow.needsRelativeLayeringToIme()); } @@ -980,4 +980,19 @@ public class WindowStateTests extends WindowTestsBase { assertNotNull(state.peekSource(ITYPE_IME)); assertTrue(state.getSource(ITYPE_IME).isVisible()); } + + @Test + public void testRequestedVisibility() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + app.mActivityRecord.setVisible(false); + app.mActivityRecord.setVisibility(false /* visible */, false /* deferHidingClient */); + assertFalse(app.isVisibleRequested()); + + // It doesn't have a surface yet, but should still be visible requested. + app.setHasSurface(false); + app.mActivityRecord.setVisibility(true /* visible */, false /* deferHidingClient */); + + assertFalse(app.isVisible()); + assertTrue(app.isVisibleRequested()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 611b3f5e62a5..b997acfaadcf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; @@ -29,11 +30,13 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.os.Process.SYSTEM_UID; import static android.view.View.VISIBLE; +import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -53,6 +56,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.StartingSurfaceController.DEBUG_ENABLE_SHELL_DRAWER; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; +import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN; import static org.junit.Assert.assertEquals; @@ -63,6 +67,7 @@ import static org.mockito.Mockito.mock; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.IApplicationThread; @@ -72,7 +77,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; -import android.graphics.Rect; +import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; import android.os.Build; import android.os.Bundle; @@ -83,10 +88,12 @@ import android.service.voice.IVoiceInteractionSession; import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; +import android.view.Gravity; import android.view.IDisplayWindowInsetsController; import android.view.IWindow; import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.View; @@ -94,6 +101,8 @@ import android.view.WindowManager; import android.view.WindowManager.DisplayImePolicy; import android.window.ITransitionPlayer; import android.window.StartingWindowInfo; +import android.window.StartingWindowRemovalInfo; +import android.window.TaskFragmentOrganizer; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; @@ -119,6 +128,9 @@ class WindowTestsBase extends SystemServiceTestsBase { // Default package name static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo"; + static final int DEFAULT_TASK_FRAGMENT_ORGANIZER_UID = 10000; + static final String DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME = "Test:TaskFragmentOrganizer"; + // Default base activity name private static final String DEFAULT_COMPONENT_CLASS_NAME = ".BarActivity"; @@ -132,6 +144,9 @@ class WindowTestsBase extends SystemServiceTestsBase { DisplayInfo mDisplayInfo = new DisplayInfo(); DisplayContent mDefaultDisplay; + static final int STATUS_BAR_HEIGHT = 10; + static final int NAV_BAR_HEIGHT = 15; + /** * It is {@link #mDefaultDisplay} by default. If the test class or method is annotated with * {@link UseTestDisplay}, it will be an additional display. @@ -185,6 +200,13 @@ class WindowTestsBase extends SystemServiceTestsBase { SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock); mDefaultDisplay = mWm.mRoot.getDefaultDisplay(); + // Update the display policy to make the screen fully turned on so animation is allowed + final DisplayPolicy displayPolicy = mDefaultDisplay.getDisplayPolicy(); + displayPolicy.screenTurnedOn(null /* screenOnListener */); + displayPolicy.finishKeyguardDrawn(); + displayPolicy.finishWindowsDrawn(); + displayPolicy.finishScreenTurningOn(); + mTransaction = mSystemServicesTestRule.mTransaction; mMockSession = mock(Session.class); @@ -212,6 +234,10 @@ class WindowTestsBase extends SystemServiceTestsBase { // {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}, // may be set on some device form factors. mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f); + // Ensure letterbox reachability treatment isn't overridden on any device target. + // {@link com.android.internal.R.bool.config_letterboxIsReachabilityEnabled}, + // may be set on some device form factors. + mAtm.mWindowManager.mLetterboxConfiguration.setIsReachabilityEnabled(false); checkDeviceSpecificOverridesNotApplied(); } @@ -219,12 +245,9 @@ class WindowTestsBase extends SystemServiceTestsBase { @After public void tearDown() throws Exception { // Revert back to device overrides. - mAtm.mWindowManager.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio( - mContext.getResources().getFloat( - com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio)); - mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier( - mContext.getResources().getFloat( - com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier)); + mAtm.mWindowManager.mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio(); + mAtm.mWindowManager.mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier(); + mAtm.mWindowManager.mLetterboxConfiguration.resetIsReachabilityEnabled(); } /** @@ -268,6 +291,14 @@ class WindowTestsBase extends SystemServiceTestsBase { } if (addAll || ArrayUtils.contains(requestedWindows, W_STATUS_BAR)) { mStatusBarWindow = createCommonWindow(null, TYPE_STATUS_BAR, "mStatusBarWindow"); + if (INSETS_LAYOUT_GENERALIZATION) { + mStatusBarWindow.mAttrs.height = STATUS_BAR_HEIGHT; + mStatusBarWindow.mAttrs.gravity = Gravity.TOP; + mStatusBarWindow.mAttrs.layoutInDisplayCutoutMode = + LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + mStatusBarWindow.setRequestedSize(WindowManager.LayoutParams.MATCH_PARENT, + STATUS_BAR_HEIGHT); + } } if (addAll || ArrayUtils.contains(requestedWindows, W_NOTIFICATION_SHADE)) { mNotificationShadeWindow = createCommonWindow(null, TYPE_NOTIFICATION_SHADE, @@ -275,6 +306,15 @@ class WindowTestsBase extends SystemServiceTestsBase { } if (addAll || ArrayUtils.contains(requestedWindows, W_NAVIGATION_BAR)) { mNavBarWindow = createCommonWindow(null, TYPE_NAVIGATION_BAR, "mNavBarWindow"); + if (INSETS_LAYOUT_GENERALIZATION) { + mNavBarWindow.mAttrs.height = NAV_BAR_HEIGHT; + mNavBarWindow.mAttrs.gravity = Gravity.BOTTOM; + mNavBarWindow.mAttrs.paramsForRotation = new WindowManager.LayoutParams[4]; + for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) { + mNavBarWindow.mAttrs.paramsForRotation[rot] = + getNavBarLayoutParamsForRotation(rot); + } + } } if (addAll || ArrayUtils.contains(requestedWindows, W_DOCK_DIVIDER)) { mDockedDividerWindow = createCommonWindow(null, TYPE_DOCK_DIVIDER, @@ -302,6 +342,37 @@ class WindowTestsBase extends SystemServiceTestsBase { waitUntilHandlersIdle(); } + private WindowManager.LayoutParams getNavBarLayoutParamsForRotation(int rotation) { + int width = WindowManager.LayoutParams.MATCH_PARENT; + int height = WindowManager.LayoutParams.MATCH_PARENT; + int gravity = Gravity.BOTTOM; + if (INSETS_LAYOUT_GENERALIZATION) { + switch (rotation) { + case ROTATION_UNDEFINED: + case Surface.ROTATION_0: + case Surface.ROTATION_180: + height = NAV_BAR_HEIGHT; + break; + case Surface.ROTATION_90: + gravity = Gravity.RIGHT; + width = NAV_BAR_HEIGHT; + break; + case Surface.ROTATION_270: + gravity = Gravity.LEFT; + width = NAV_BAR_HEIGHT; + break; + } + } + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR); + lp.width = width; + lp.height = height; + if (INSETS_LAYOUT_GENERALIZATION) { + lp.gravity = gravity; + } + return lp; + } + void beforeCreateTestDisplay() { // Called before display is created. } @@ -533,7 +604,7 @@ class WindowTestsBase extends SystemServiceTestsBase { Task createTaskInRootTask(Task rootTask, int userId) { final Task task = new TaskBuilder(rootTask.mTaskSupervisor) .setUserId(userId) - .setParentTask(rootTask) + .setParentTaskFragment(rootTask) .build(); return task; } @@ -619,6 +690,35 @@ class WindowTestsBase extends SystemServiceTestsBase { activity.mVisibleRequested = true; } + /** + * Creates a {@link TaskFragment} and attach it to the {@code parentTask}. + * + * @param parentTask the {@link Task} this TaskFragment is going to be attached + * @param createEmbeddedTask Sets to {@code true} to create an embedded Task for this + * TaskFragment. Otherwise, create a {@link ActivityRecord}. + * @return the created TaskFragment + */ + static TaskFragment createTaskFragmentWithParentTask(@NonNull Task parentTask, + boolean createEmbeddedTask) { + final TaskFragmentBuilder builder = new TaskFragmentBuilder(parentTask.mAtmService) + .setParentTask(parentTask); + if (createEmbeddedTask) { + builder.createEmbeddedTask(); + } else { + builder.createActivityCount(1); + } + return builder.build(); + } + + static TaskFragment createTaskFragmentWithEmbeddedActivity(@NonNull Task parentTask, + TaskFragmentOrganizer organizer) { + return new TaskFragmentBuilder(parentTask.mAtmService) + .setParentTask(parentTask) + .createActivityCount(1) + .setOrganizer(organizer) + .build(); + } + /** Creates a {@link DisplayContent} that supports IME and adds it to the system. */ DisplayContent createNewDisplay() { return createNewDisplayWithImeSupport(DISPLAY_IME_POLICY_LOCAL); @@ -700,6 +800,23 @@ class WindowTestsBase extends SystemServiceTestsBase { }; } + BLASTSyncEngine createTestBLASTSyncEngine() { + return new BLASTSyncEngine(mWm) { + @Override + void scheduleTimeout(SyncGroup s, long timeoutMs) { + // Disable timeout. + } + }; + } + + /** Sets up a simple implementation of transition player for shell transitions. */ + TestTransitionPlayer registerTestTransitionPlayer() { + final TestTransitionPlayer testPlayer = new TestTransitionPlayer( + mAtm.getTransitionController(), mAtm.mWindowOrganizerController); + testPlayer.mController.registerTransitionPlayer(testPlayer, null /* appThread */); + return testPlayer; + } + /** * Avoids rotating screen disturbed by some conditions. It is usually used for the default * display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions). @@ -772,6 +889,21 @@ class WindowTestsBase extends SystemServiceTestsBase { mAtm.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp = 1; } + /** Mocks the behavior of taking a snapshot. */ + void mockSurfaceFreezerSnapshot(SurfaceFreezer surfaceFreezer) { + final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = + mock(SurfaceControl.ScreenshotHardwareBuffer.class); + final HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); + spyOn(surfaceFreezer); + doReturn(screenshotBuffer).when(surfaceFreezer) + .createSnapshotBufferInner(any(), any()); + doReturn(null).when(surfaceFreezer) + .createFromHardwareBufferInner(any()); + doReturn(hardwareBuffer).when(screenshotBuffer).getHardwareBuffer(); + doReturn(100).when(hardwareBuffer).getWidth(); + doReturn(100).when(hardwareBuffer).getHeight(); + } + /** * Builder for creating new activities. */ @@ -992,7 +1124,7 @@ class WindowTestsBase extends SystemServiceTestsBase { // Apply the root activity info and intent .setActivityInfo(aInfo) .setIntent(intent) - .setParentTask(mParentTask).build(); + .setParentTaskFragment(mParentTask).build(); } else if (mTask == null && mParentTask != null && DisplayContent.alwaysCreateRootTask( mParentTask.getWindowingMode(), mParentTask.getActivityType())) { // The parent task can be the task root. @@ -1052,6 +1184,82 @@ class WindowTestsBase extends SystemServiceTestsBase { } } + static class TaskFragmentBuilder { + private final ActivityTaskManagerService mAtm; + private Task mParentTask; + private boolean mCreateParentTask; + private boolean mCreateEmbeddedTask; + private int mCreateActivityCount = 0; + @Nullable + private TaskFragmentOrganizer mOrganizer; + private IBinder mFragmentToken; + + TaskFragmentBuilder(ActivityTaskManagerService service) { + mAtm = service; + } + + TaskFragmentBuilder setCreateParentTask() { + mCreateParentTask = true; + return this; + } + + TaskFragmentBuilder setParentTask(Task task) { + mParentTask = task; + return this; + } + + /** Creates a child embedded Task and its Activity */ + TaskFragmentBuilder createEmbeddedTask() { + mCreateEmbeddedTask = true; + return this; + } + + TaskFragmentBuilder createActivityCount(int count) { + mCreateActivityCount = count; + return this; + } + + TaskFragmentBuilder setOrganizer(@Nullable TaskFragmentOrganizer organizer) { + mOrganizer = organizer; + return this; + } + + TaskFragmentBuilder setFragmentToken(@Nullable IBinder fragmentToken) { + mFragmentToken = fragmentToken; + return this; + } + + TaskFragment build() { + SystemServicesTestRule.checkHoldsLock(mAtm.mGlobalLock); + + final TaskFragment taskFragment = new TaskFragment(mAtm, mFragmentToken, + mOrganizer != null); + if (mParentTask == null && mCreateParentTask) { + mParentTask = new TaskBuilder(mAtm.mTaskSupervisor).build(); + } + if (mParentTask != null) { + mParentTask.addChild(taskFragment, POSITION_TOP); + } + if (mCreateEmbeddedTask) { + new TaskBuilder(mAtm.mTaskSupervisor) + .setParentTaskFragment(taskFragment) + .setCreateActivity(true) + .build(); + } + while (mCreateActivityCount > 0) { + final ActivityRecord activity = new ActivityBuilder(mAtm).build(); + taskFragment.addChild(activity); + mCreateActivityCount--; + } + if (mOrganizer != null) { + taskFragment.setTaskFragmentOrganizer( + mOrganizer.getOrganizerToken(), DEFAULT_TASK_FRAGMENT_ORGANIZER_UID, + DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME); + } + return taskFragment; + } + } + /** * Builder for creating new tasks. */ @@ -1072,7 +1280,7 @@ class WindowTestsBase extends SystemServiceTestsBase { private IVoiceInteractionSession mVoiceSession; private boolean mCreateParentTask = false; - private Task mParentTask; + private TaskFragment mParentTaskFragment; private boolean mCreateActivity = false; @@ -1156,8 +1364,8 @@ class WindowTestsBase extends SystemServiceTestsBase { return this; } - TaskBuilder setParentTask(Task parentTask) { - mParentTask = parentTask; + TaskBuilder setParentTaskFragment(TaskFragment parentTaskFragment) { + mParentTaskFragment = parentTaskFragment; return this; } @@ -1170,12 +1378,13 @@ class WindowTestsBase extends SystemServiceTestsBase { SystemServicesTestRule.checkHoldsLock(mSupervisor.mService.mGlobalLock); // Create parent task. - if (mParentTask == null && mCreateParentTask) { - mParentTask = mTaskDisplayArea.createRootTask( + if (mParentTaskFragment == null && mCreateParentTask) { + mParentTaskFragment = mTaskDisplayArea.createRootTask( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); } - if (mParentTask != null && !Mockito.mockingDetails(mParentTask).isSpy()) { - spyOn(mParentTask); + if (mParentTaskFragment != null + && !Mockito.mockingDetails(mParentTaskFragment).isSpy()) { + spyOn(mParentTaskFragment); } // Create task. @@ -1203,13 +1412,15 @@ class WindowTestsBase extends SystemServiceTestsBase { .setOnTop(mOnTop) .setVoiceSession(mVoiceSession); final Task task; - if (mParentTask == null) { + if (mParentTaskFragment == null) { task = builder.setActivityType(mActivityType) .setParent(mTaskDisplayArea) .build(); } else { - task = builder.setParent(mParentTask).build(); - mParentTask.moveToFront("build-task"); + task = builder.setParent(mParentTaskFragment).build(); + if (mParentTaskFragment.asTask() != null) { + mParentTaskFragment.asTask().moveToFront("build-task"); + } } spyOn(task); task.mUserId = mUserId; @@ -1290,12 +1501,11 @@ class WindowTestsBase extends SystemServiceTestsBase { } } @Override - public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, - boolean playRevealAnimation) { + public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { synchronized (mWMService.mGlobalLock) { - final IBinder appToken = mTaskAppMap.get(taskId); + final IBinder appToken = mTaskAppMap.get(removalInfo.taskId); if (appToken != null) { - mTaskAppMap.remove(taskId); + mTaskAppMap.remove(removalInfo.taskId); final ActivityRecord activity = mWMService.mRoot.getActivityRecord( appToken); WindowState win = mAppWindowMap.remove(appToken); @@ -1431,7 +1641,7 @@ class WindowTestsBase extends SystemServiceTestsBase { } } - class TestTransitionPlayer extends ITransitionPlayer.Stub { + static class TestTransitionPlayer extends ITransitionPlayer.Stub { final TransitionController mController; final WindowOrganizerController mOrganizer; Transition mLastTransit = null; diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java index d967891fdb76..4dffe7e8345b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -20,9 +20,10 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; @@ -37,10 +38,13 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static com.android.server.wm.WindowStateAnimator.PRESERVED_SURFACE_LAYER; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -293,6 +297,7 @@ public class ZOrderingTests extends WindowTestsBase { final WindowState appAboveImeTarget = createWindow("appAboveImeTarget"); mDisplayContent.setImeLayeringTarget(imeAppTarget); + mDisplayContent.setImeControlTarget(imeAppTarget); mDisplayContent.assignChildLayers(mTransaction); // Ime should be above all app windows except for non-fullscreen app window above it and @@ -339,6 +344,7 @@ public class ZOrderingTests extends WindowTestsBase { @Test public void testAssignWindowLayers_ForStatusBarImeTarget() { mDisplayContent.setImeLayeringTarget(mStatusBarWindow); + mDisplayContent.setImeControlTarget(mStatusBarWindow); mDisplayContent.assignChildLayers(mTransaction); assertWindowHigher(mImeWindow, mChildAppWindowAbove); @@ -399,6 +405,50 @@ public class ZOrderingTests extends WindowTestsBase { } @Test + public void testAssignWindowLayers_ForImeOnAppWithRecentsAnimating() { + final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION, + mAppWindow.mActivityRecord, "imeAppTarget"); + mDisplayContent.setImeInputTarget(imeAppTarget); + mDisplayContent.setImeLayeringTarget(imeAppTarget); + mDisplayContent.setImeControlTarget(imeAppTarget); + mDisplayContent.updateImeParent(); + + // Simulate the ime layering target task is animating with recents animation. + final Task imeAppTargetTask = imeAppTarget.getTask(); + final SurfaceAnimator imeTargetTaskAnimator = imeAppTargetTask.mSurfaceAnimator; + spyOn(imeTargetTaskAnimator); + doReturn(ANIMATION_TYPE_RECENTS).when(imeTargetTaskAnimator).getAnimationType(); + doReturn(true).when(imeTargetTaskAnimator).isAnimating(); + + mDisplayContent.assignChildLayers(mTransaction); + + // Ime should on top of the application window when in recents animation and keep + // attached on app. + assertTrue(mDisplayContent.shouldImeAttachedToApp()); + assertWindowHigher(mImeWindow, imeAppTarget); + } + + @Test + public void testAssignWindowLayers_ForImeOnPopupImeLayeringTarget() { + final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION, + mAppWindow.mActivityRecord, "imeAppTarget"); + mDisplayContent.setImeInputTarget(imeAppTarget); + mDisplayContent.setImeLayeringTarget(imeAppTarget); + mDisplayContent.setImeControlTarget(imeAppTarget); + + // Set a popup IME layering target and keeps the original IME control target behinds it. + final WindowState popupImeTargetWin = createWindow(imeAppTarget, + TYPE_APPLICATION_SUB_PANEL, mAppWindow.mActivityRecord, "popupImeTargetWin"); + mDisplayContent.setImeLayeringTarget(popupImeTargetWin); + mDisplayContent.updateImeParent(); + + // Ime should on top of the popup IME layering target window. + mDisplayContent.assignChildLayers(mTransaction); + assertWindowHigher(mImeWindow, popupImeTargetWin); + } + + + @Test public void testAssignWindowLayers_ForNegativelyZOrderedSubtype() { // TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA // then we can drop all negative layering on the windowing side. @@ -440,24 +490,73 @@ public class ZOrderingTests extends WindowTestsBase { @Test public void testDockedDividerPosition() { - final WindowState pinnedStackWindow = createWindow(null, WINDOWING_MODE_PINNED, - ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, - "pinnedStackWindow"); - final WindowState splitScreenWindow = createWindow(null, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, - mDisplayContent, "splitScreenWindow"); - final WindowState splitScreenSecondaryWindow = createWindow(null, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, - TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow"); - final WindowState assistantStackWindow = createWindow(null, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION, - mDisplayContent, "assistantStackWindow"); + final Task pinnedTask = + createTask(mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD); + final WindowState pinnedWindow = + createAppWindow(pinnedTask, ACTIVITY_TYPE_STANDARD, "pinnedWindow"); + + final Task belowTask = + createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final WindowState belowTaskWindow = + createAppWindow(belowTask, ACTIVITY_TYPE_STANDARD, "belowTaskWindow"); + + final Task splitScreenTask1 = + createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); + final WindowState splitWindow1 = + createAppWindow(splitScreenTask1, ACTIVITY_TYPE_STANDARD, "splitWindow1"); + final Task splitScreenTask2 = + createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); + final WindowState splitWindow2 = + createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2"); + splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2, true /* moveTogether */); + splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1, true /* moveTogether */); + + final Task aboveTask = + createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final WindowState aboveTaskWindow = + createAppWindow(aboveTask, ACTIVITY_TYPE_STANDARD, "aboveTaskWindow"); mDisplayContent.assignChildLayers(mTransaction); - assertWindowHigher(mDockedDividerWindow, splitScreenWindow); - assertWindowHigher(mDockedDividerWindow, splitScreenSecondaryWindow); - assertWindowHigher(pinnedStackWindow, mDockedDividerWindow); + assertWindowHigher(splitWindow1, belowTaskWindow); + assertWindowHigher(splitWindow2, belowTaskWindow); + assertWindowHigher(mDockedDividerWindow, splitWindow1); + assertWindowHigher(mDockedDividerWindow, splitWindow2); + assertWindowHigher(aboveTaskWindow, mDockedDividerWindow); + assertWindowHigher(pinnedWindow, aboveTaskWindow); + } + + + @Test + public void testDockedDividerPosition_noAboveTask() { + final Task pinnedTask = + createTask(mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD); + final WindowState pinnedWindow = + createAppWindow(pinnedTask, ACTIVITY_TYPE_STANDARD, "pinnedWindow"); + + final Task belowTask = + createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final WindowState belowTaskWindow = + createAppWindow(belowTask, ACTIVITY_TYPE_STANDARD, "belowTaskWindow"); + + final Task splitScreenTask1 = + createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); + final WindowState splitWindow1 = + createAppWindow(splitScreenTask1, ACTIVITY_TYPE_STANDARD, "splitWindow1"); + final Task splitScreenTask2 = + createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); + final WindowState splitWindow2 = + createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2"); + splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2, true /* moveTogether */); + splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1, true /* moveTogether */); + + mDisplayContent.assignChildLayers(mTransaction); + + assertWindowHigher(splitWindow1, belowTaskWindow); + assertWindowHigher(splitWindow2, belowTaskWindow); + assertWindowHigher(mDockedDividerWindow, splitWindow1); + assertWindowHigher(mDockedDividerWindow, splitWindow2); + assertWindowHigher(pinnedWindow, mDockedDividerWindow); } @Test @@ -493,4 +592,27 @@ public class ZOrderingTests extends WindowTestsBase { assertZOrderGreaterThan(mTransaction, mNavBarWindow.mToken.getSurfaceControl(), mDisplayContent.getImeContainer().getSurfaceControl()); } + + @Test + public void testPopupWindowAndParentIsImeTarget_expectHigherThanIme_inMultiWindow() { + // Simulate the app window is in multi windowing mode and being IME target + mAppWindow.getConfiguration().windowConfiguration.setWindowingMode( + WINDOWING_MODE_MULTI_WINDOW); + mDisplayContent.setImeLayeringTarget(mAppWindow); + mDisplayContent.setImeInputTarget(mAppWindow); + + // Create a popupWindow + assertWindowHigher(mImeWindow, mAppWindow); + final WindowState popupWindow = createWindow(mAppWindow, TYPE_APPLICATION_PANEL, + mDisplayContent, "PopupWindow"); + spyOn(popupWindow); + + mDisplayContent.assignChildLayers(mTransaction); + + // Verify the surface layer of the popupWindow should higher than IME + verify(popupWindow).needsRelativeLayeringToIme(); + assertThat(popupWindow.needsRelativeLayeringToIme()).isTrue(); + assertZOrderGreaterThan(mTransaction, popupWindow.getSurfaceControl(), + mDisplayContent.getImeContainer().getSurfaceControl()); + } } diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index ba6fe4812793..7f151265380e 100755 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -177,10 +177,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser // Delay for debouncing USB disconnects. // We often get rapid connect/disconnect events when enabling USB functions, // which need debouncing. - private static final int DEVICE_STATE_UPDATE_DELAY = 3000; - - // Delay for debouncing USB disconnects on Type-C ports in host mode - private static final int HOST_STATE_UPDATE_DELAY = 1000; + private static final int UPDATE_DELAY = 1000; // Timeout for entering USB request mode. // Request is cancelled if host does not configure device within 10 seconds. @@ -642,7 +639,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser msg.arg1 = connected; msg.arg2 = configured; // debounce disconnects to avoid problems bringing up USB tethering - sendMessageDelayed(msg, (connected == 0) ? DEVICE_STATE_UPDATE_DELAY : 0); + sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0); } public void updateHostState(UsbPort port, UsbPortStatus status) { @@ -657,7 +654,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser removeMessages(MSG_UPDATE_PORT_STATE); Message msg = obtainMessage(MSG_UPDATE_PORT_STATE, args); // debounce rapid transitions of connect/disconnect on type-c ports - sendMessageDelayed(msg, HOST_STATE_UPDATE_DELAY); + sendMessageDelayed(msg, UPDATE_DELAY); } private void setAdbEnabled(boolean enable) { diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java index 0c65cc40bd82..286cff90daab 100644 --- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java +++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java @@ -688,6 +688,8 @@ class UsbUserPermissionManager { String packageName, PendingIntent pi, int uid) { + boolean throwException = false; + // compare uid with packageName to foil apps pretending to be someone else try { ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0); @@ -695,11 +697,13 @@ class UsbUserPermissionManager { Slog.w(TAG, "package " + packageName + " does not match caller's uid " + uid); EventLog.writeEvent(SNET_EVENT_LOG_ID, "180104273", -1, ""); - throw new IllegalArgumentException("package " + packageName - + " not found"); + throwException = true; } } catch (PackageManager.NameNotFoundException e) { - throw new IllegalArgumentException("package " + packageName + " not found"); + throwException = true; + } finally { + if (throwException) + throw new IllegalArgumentException("package " + packageName + " not found"); } requestPermissionDialog(device, accessory, canBeDefault, packageName, uid, mContext, pi); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 4dc83ae98d89..36bb375be3c8 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -838,7 +838,7 @@ final class HotwordDetectionConnection { try { return mContext.bindIsolatedService( mIntent, - Context.BIND_AUTO_CREATE | mBindingFlags, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | mBindingFlags, "hotword_detector_" + mInstanceNumber, mExecutor, serviceConnection); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 9ea2b7b12ad0..8445ed4884e2 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -167,6 +167,16 @@ public class VoiceInteractionManagerService extends SystemService { public void onStart() { publishBinderService(Context.VOICE_INTERACTION_MANAGER_SERVICE, mServiceStub); publishLocalService(VoiceInteractionManagerInternal.class, new LocalService()); + mAmInternal.setVoiceInteractionManagerProvider( + new ActivityManagerInternal.VoiceInteractionManagerProvider() { + @Override + public void notifyActivityEventChanged() { + if (DEBUG) { + Slog.d(TAG, "call notifyActivityEventChanged"); + } + mServiceStub.notifyActivityEventChanged(); + } + }); } @Override @@ -386,6 +396,14 @@ public class VoiceInteractionManagerService extends SystemService { return mImpl.supportsLocalVoiceInteraction(); } + void notifyActivityEventChanged() { + synchronized (this) { + if (mImpl == null) return; + + Binder.withCleanCallingIdentity(() -> mImpl.notifyActivityEventChangedLocked()); + } + } + @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { @@ -1109,6 +1127,40 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Override + public void startListeningVisibleActivityChanged(@NonNull IBinder token) { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "startListeningVisibleActivityChanged without running" + + " voice interaction service"); + return; + } + final long caller = Binder.clearCallingIdentity(); + try { + mImpl.startListeningVisibleActivityChangedLocked(token); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override + public void stopListeningVisibleActivityChanged(@NonNull IBinder token) { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "stopListeningVisibleActivityChanged without running" + + " voice interaction service"); + return; + } + final long caller = Binder.clearCallingIdentity(); + try { + mImpl.stopListeningVisibleActivityChangedLocked(token); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + //----------------- Hotword Detection/Validation APIs --------------------------------// @Override diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 558a9ac9298e..52c5b6bbc239 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -414,6 +414,44 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return mInfo.getSupportsLocalInteraction(); } + public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) { + if (DEBUG) { + Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token); + } + if (mActiveSession == null || token != mActiveSession.mToken) { + Slog.w(TAG, "startListeningVisibleActivityChangedLocked does not match" + + " active session"); + return; + } + mActiveSession.startListeningVisibleActivityChangedLocked(); + } + + public void stopListeningVisibleActivityChangedLocked(@NonNull IBinder token) { + if (DEBUG) { + Slog.d(TAG, "stopListeningVisibleActivityChangedLocked: token=" + token); + } + if (mActiveSession == null || token != mActiveSession.mToken) { + Slog.w(TAG, "stopListeningVisibleActivityChangedLocked does not match" + + " active session"); + return; + } + mActiveSession.stopListeningVisibleActivityChangedLocked(); + } + + public void notifyActivityEventChangedLocked() { + if (DEBUG) { + Slog.d(TAG, "notifyActivityEventChangedLocked"); + } + if (mActiveSession == null || !mActiveSession.mShown) { + if (DEBUG) { + Slog.d(TAG, "notifyActivityEventChangedLocked not allowed on no session or" + + " hidden session"); + } + return; + } + mActiveSession.notifyActivityEventChangedLocked(); + } + public void updateStateLocked( @NonNull Identity voiceInteractorIdentity, @Nullable PersistableBundle options, diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java index 08e9703124ab..90ccec852e1e 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java @@ -56,6 +56,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.service.voice.IVoiceInteractionSession; import android.service.voice.IVoiceInteractionSessionService; +import android.service.voice.VisibleActivityInfo; import android.service.voice.VoiceInteractionService; import android.service.voice.VoiceInteractionSession; import android.util.Slog; @@ -71,16 +72,20 @@ import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.wm.ActivityAssistInfo; +import com.android.server.wm.ActivityTaskManagerInternal; import java.io.PrintWriter; import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; final class VoiceInteractionSessionConnection implements ServiceConnection, AssistDataRequesterCallbacks { static final String TAG = "VoiceInteractionServiceManager"; + static final boolean DEBUG = false; static final int POWER_BOOST_TIMEOUT_MS = Integer.parseInt( System.getProperty("vendor.powerhal.interaction.max", "200")); static final int BOOST_TIMEOUT_MS = 300; @@ -114,6 +119,10 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>(); private List<ActivityAssistInfo> mPendingHandleAssistWithoutData = new ArrayList<>(); AssistDataRequester mAssistDataRequester; + private boolean mListeningVisibleActivity; + private final ScheduledExecutorService mScheduledExecutorService = + Executors.newSingleThreadScheduledExecutor(); + private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>(); private final PowerManagerInternal mPowerManagerInternal; private PowerBoostSetter mSetPowerBoostRunnable; private final Handler mFgHandler; @@ -496,6 +505,8 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, } public void cancelLocked(boolean finishTask) { + mListeningVisibleActivity = false; + mVisibleActivityInfos.clear(); hideLocked(); mCanceled = true; if (mBound) { @@ -569,6 +580,156 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, mPendingShowCallbacks.clear(); } + void startListeningVisibleActivityChangedLocked() { + if (DEBUG) { + Slog.d(TAG, "startListeningVisibleActivityChangedLocked"); + } + mListeningVisibleActivity = true; + mVisibleActivityInfos.clear(); + + mScheduledExecutorService.execute(() -> { + if (DEBUG) { + Slog.d(TAG, "call updateVisibleActivitiesLocked from enable listening"); + } + synchronized (mLock) { + updateVisibleActivitiesLocked(); + } + }); + } + + void stopListeningVisibleActivityChangedLocked() { + if (DEBUG) { + Slog.d(TAG, "stopListeningVisibleActivityChangedLocked"); + } + mListeningVisibleActivity = false; + mVisibleActivityInfos.clear(); + } + + void notifyActivityEventChangedLocked() { + if (DEBUG) { + Slog.d(TAG, "notifyActivityEventChangedLocked"); + } + if (!mListeningVisibleActivity) { + if (DEBUG) { + Slog.d(TAG, "not enable listening visible activity"); + } + return; + } + mScheduledExecutorService.execute(() -> { + if (DEBUG) { + Slog.d(TAG, "call updateVisibleActivitiesLocked from activity event"); + } + synchronized (mLock) { + updateVisibleActivitiesLocked(); + } + }); + } + + private List<VisibleActivityInfo> getVisibleActivityInfosLocked() { + if (DEBUG) { + Slog.d(TAG, "getVisibleActivityInfosLocked"); + } + List<ActivityAssistInfo> allVisibleActivities = + LocalServices.getService(ActivityTaskManagerInternal.class) + .getTopVisibleActivities(); + if (DEBUG) { + Slog.d(TAG, + "getVisibleActivityInfosLocked: allVisibleActivities=" + allVisibleActivities); + } + if (allVisibleActivities == null || allVisibleActivities.isEmpty()) { + Slog.w(TAG, "no visible activity"); + return null; + } + final int count = allVisibleActivities.size(); + final List<VisibleActivityInfo> visibleActivityInfos = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + ActivityAssistInfo info = allVisibleActivities.get(i); + if (DEBUG) { + Slog.d(TAG, " : activityToken=" + info.getActivityToken() + + ", assistToken=" + info.getAssistToken() + + ", taskId=" + info.getTaskId()); + } + visibleActivityInfos.add( + new VisibleActivityInfo(info.getTaskId(), info.getAssistToken())); + } + return visibleActivityInfos; + } + + private void updateVisibleActivitiesLocked() { + if (DEBUG) { + Slog.d(TAG, "updateVisibleActivitiesLocked"); + } + if (mSession == null) { + return; + } + if (!mShown || !mListeningVisibleActivity || mCanceled) { + return; + } + final List<VisibleActivityInfo> newVisibleActivityInfos = getVisibleActivityInfosLocked(); + + if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) { + updateVisibleActivitiesChangedLocked(mVisibleActivityInfos, + VisibleActivityInfo.TYPE_ACTIVITY_REMOVED); + mVisibleActivityInfos.clear(); + return; + } + if (mVisibleActivityInfos.isEmpty()) { + updateVisibleActivitiesChangedLocked(newVisibleActivityInfos, + VisibleActivityInfo.TYPE_ACTIVITY_ADDED); + mVisibleActivityInfos.addAll(newVisibleActivityInfos); + return; + } + + final List<VisibleActivityInfo> addedActivities = new ArrayList<>(); + final List<VisibleActivityInfo> removedActivities = new ArrayList<>(); + + removedActivities.addAll(mVisibleActivityInfos); + for (int i = 0; i < newVisibleActivityInfos.size(); i++) { + final VisibleActivityInfo candidateVisibleActivityInfo = newVisibleActivityInfos.get(i); + if (!removedActivities.isEmpty() && removedActivities.contains( + candidateVisibleActivityInfo)) { + removedActivities.remove(candidateVisibleActivityInfo); + } else { + addedActivities.add(candidateVisibleActivityInfo); + } + } + + if (!addedActivities.isEmpty()) { + updateVisibleActivitiesChangedLocked(addedActivities, + VisibleActivityInfo.TYPE_ACTIVITY_ADDED); + } + if (!removedActivities.isEmpty()) { + updateVisibleActivitiesChangedLocked(removedActivities, + VisibleActivityInfo.TYPE_ACTIVITY_REMOVED); + } + + mVisibleActivityInfos.clear(); + mVisibleActivityInfos.addAll(newVisibleActivityInfos); + } + + private void updateVisibleActivitiesChangedLocked( + List<VisibleActivityInfo> visibleActivityInfos, int type) { + if (visibleActivityInfos == null || visibleActivityInfos.isEmpty()) { + return; + } + if (mSession == null) { + return; + } + try { + for (int i = 0; i < visibleActivityInfos.size(); i++) { + mSession.updateVisibleActivityInfo(visibleActivityInfos.get(i), type); + } + } catch (RemoteException e) { + if (DEBUG) { + Slog.w(TAG, "updateVisibleActivitiesChangedLocked RemoteException : " + e); + } + } + if (DEBUG) { + Slog.d(TAG, "updateVisibleActivitiesChangedLocked type=" + type + ", count=" + + visibleActivityInfos.size()); + } + } + @Override public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mLock) { |