diff options
Diffstat (limited to 'services')
244 files changed, 7489 insertions, 3882 deletions
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 529a564ea607..5e1fe8a60973 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -11,6 +11,16 @@ flag { } flag { + name: "allow_secure_screenshots" + namespace: "accessibility" + description: "Allow certain AccessibilityServices to take screenshots of FLAG_SECURE screens" + bug: "373705911" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "always_allow_observing_touch_events" namespace: "accessibility" description: "Always allows InputFilter observing SOURCE_TOUCHSCREEN events, even if touch exploration is enabled." @@ -145,6 +155,13 @@ flag { } flag { + name: "enable_magnification_follows_mouse_with_pointer_motion_filter" + namespace: "accessibility" + description: "Whether to enable mouse following using pointer motion filter" + bug: "361817142" +} + +flag { name: "enable_magnification_keyboard_control" namespace: "accessibility" description: "Whether to enable keyboard control for magnification" diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 9ceca5d1dbfe..4b042489f3eb 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -86,11 +86,13 @@ import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; +import android.view.WindowManager; import android.view.accessibility.AccessibilityCache; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; +import android.view.accessibility.IWindowSurfaceInfoCallback; import android.view.inputmethod.EditorInfo; import android.window.ScreenCapture; import android.window.ScreenCapture.ScreenshotHardwareBuffer; @@ -343,6 +345,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ SurfaceControl sc, IAccessibilityInteractionConnectionCallback callback); + int performScreenCapture(ScreenCapture.LayerCaptureArgs captureArgs, + ScreenCapture.ScreenCaptureListener captureListener); } public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName, @@ -1456,7 +1460,45 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_WINDOW, interactionId); return; } - connection.getRemote().takeScreenshotOfWindow(interactionId, listener, callback); + if (Flags.allowSecureScreenshots()) { + IWindowSurfaceInfoCallback infoCallback = new IWindowSurfaceInfoCallback.Stub() { + @Override + public void provideWindowSurfaceInfo(int windowFlags, int processUid, + SurfaceControl surfaceControl) { + final boolean canCaptureSecureLayers = canCaptureSecureLayers(); + if (!canCaptureSecureLayers + && (windowFlags & WindowManager.LayoutParams.FLAG_SECURE) != 0) { + try { + callback.sendTakeScreenshotOfWindowError( + AccessibilityService.ERROR_TAKE_SCREENSHOT_SECURE_WINDOW, + interactionId); + } catch (RemoteException e) { + // ignore - the other side will time out + } + return; + } + + final ScreenCapture.LayerCaptureArgs captureArgs = + new ScreenCapture.LayerCaptureArgs.Builder(surfaceControl) + .setChildrenOnly(false) + .setUid(processUid) + .setCaptureSecureLayers(canCaptureSecureLayers) + .build(); + if (mSystemSupport.performScreenCapture(captureArgs, listener) != 0) { + try { + callback.sendTakeScreenshotOfWindowError( + AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, + interactionId); + } catch (RemoteException e) { + // ignore - the other side will time out + } + } + } + }; + connection.getRemote().getWindowSurfaceInfo(infoCallback); + } else { + connection.getRemote().takeScreenshotOfWindow(interactionId, listener, callback); + } } finally { Binder.restoreCallingIdentity(identity); } @@ -1523,7 +1565,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } ); - mWindowManagerService.captureDisplay(displayId, null, screenCaptureListener); + if (Flags.allowSecureScreenshots()) { + mWindowManagerService.captureDisplay(displayId, + new ScreenCapture.CaptureArgs.Builder<>() + .setCaptureSecureLayers(canCaptureSecureLayers()).build(), + screenCaptureListener); + } else { + mWindowManagerService.captureDisplay(displayId, null, screenCaptureListener); + } } catch (Exception e) { sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, callback); @@ -1564,6 +1613,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ }, null).recycleOnUse()); } + private boolean canCaptureSecureLayers() { + return Flags.allowSecureScreenshots() + && mAccessibilityServiceInfo.isAccessibilityTool() + && mAccessibilityServiceInfo.getResolveInfo().serviceInfo + .applicationInfo.isSystemApp(); + } + @Override @PermissionManuallyEnforced public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index e422fef6c22c..9b5f22afb81d 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -134,7 +134,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 0x00000080; -/** + /** * Flag for enabling multi-finger gestures. * * @see #setUserAndEnabledFeatures(int, int) @@ -190,8 +190,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private final AccessibilityManagerService mAms; - private final InputManager mInputManager; - private final SparseArray<EventStreamTransformation> mEventHandler; private final SparseArray<TouchExplorer> mTouchExplorer = new SparseArray<>(0); @@ -294,7 +292,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo mContext = context; mAms = service; mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - mInputManager = context.getSystemService(InputManager.class); mEventHandler = eventHandler; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 67fdca446ba4..9eb8442be783 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -173,6 +173,7 @@ import android.view.accessibility.IAccessibilityManagerClient; import android.view.accessibility.IMagnificationConnection; import android.view.accessibility.IUserInitializationCompleteCallback; import android.view.inputmethod.EditorInfo; +import android.window.ScreenCapture; import com.android.internal.R; import com.android.internal.accessibility.AccessibilityShortcutController; @@ -6725,6 +6726,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub callback)); } + @Override + public int performScreenCapture(ScreenCapture.LayerCaptureArgs captureArgs, + ScreenCapture.ScreenCaptureListener captureListener) { + return ScreenCapture.captureLayers(captureArgs, captureListener); + } + @VisibleForTesting int getShortcutTypeForGenericShortcutCalls(int userId) { int navigationMode = Settings.Secure.getIntForUser( diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java index 5283df5ca7e1..aa82df493f84 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java @@ -101,8 +101,15 @@ public class AutoclickController extends BaseEventStreamTransformation { } @Override - public void toggleAutoclickPause() { - // TODO(b/388872274): allows users to pause the autoclick. + public void toggleAutoclickPause(boolean paused) { + if (paused) { + if (mClickScheduler != null) { + mClickScheduler.cancel(); + } + if (mAutoclickIndicatorScheduler != null) { + mAutoclickIndicatorScheduler.cancel(); + } + } } }; @@ -133,7 +140,9 @@ public class AutoclickController extends BaseEventStreamTransformation { mAutoclickIndicatorScheduler); } - handleMouseMotion(event, policyFlags); + if (!isPaused()) { + handleMouseMotion(event, policyFlags); + } } else if (mClickScheduler != null) { mClickScheduler.cancel(); } @@ -216,6 +225,11 @@ public class AutoclickController extends BaseEventStreamTransformation { } } + private boolean isPaused() { + // TODO (b/397460424): Unpause when hovering over panel. + return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused(); + } + /** * Observes autoclick setting values, and updates ClickScheduler delay and indicator size * whenever the setting value changes. @@ -538,6 +552,11 @@ public class AutoclickController extends BaseEventStreamTransformation { return mActive; } + @VisibleForTesting + long getScheduledClickTimeForTesting() { + return mScheduledClickTime; + } + /** * Updates delay that should be used when scheduling clicks. The delay will be used only for * clicks scheduled after this point (pending click tasks are not affected). diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java index 614b2285d6e0..ba3e3d14b9c6 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java @@ -46,6 +46,11 @@ public class AutoclickTypePanel { public static final int AUTOCLICK_TYPE_DRAG = 3; public static final int AUTOCLICK_TYPE_SCROLL = 4; + public static final int CORNER_BOTTOM_RIGHT = 0; + public static final int CORNER_BOTTOM_LEFT = 1; + public static final int CORNER_TOP_LEFT = 2; + public static final int CORNER_TOP_RIGHT = 3; + // Types of click the AutoclickTypePanel supports. @IntDef({ AUTOCLICK_TYPE_LEFT_CLICK, @@ -56,14 +61,38 @@ public class AutoclickTypePanel { }) public @interface AutoclickType {} + @IntDef({ + CORNER_BOTTOM_RIGHT, + CORNER_BOTTOM_LEFT, + CORNER_TOP_LEFT, + CORNER_TOP_RIGHT + }) + public @interface Corner {} + + private static final @Corner int[] CORNER_ROTATION_ORDER = { + CORNER_BOTTOM_RIGHT, + CORNER_BOTTOM_LEFT, + CORNER_TOP_LEFT, + CORNER_TOP_RIGHT + }; + // An interface exposed to {@link AutoclickController) to handle different actions on the panel, // including changing autoclick type, pausing/resuming autoclick. public interface ClickPanelControllerInterface { - // Allows users to change a different autoclick type. + /** + * Allows users to change a different autoclick type. + * + * @param clickType The new autoclick type to use. Should be one of the values defined in + * {@link AutoclickType}. + */ void handleAutoclickTypeChange(@AutoclickType int clickType); - // Allows users to pause/resume the autoclick. - void toggleAutoclickPause(); + /** + * Allows users to pause or resume autoclick. + * + * @param paused {@code true} to pause autoclick, {@code false} to resume. + */ + void toggleAutoclickPause(boolean paused); } private final Context mContext; @@ -77,14 +106,26 @@ public class AutoclickTypePanel { // Whether the panel is expanded or not. private boolean mExpanded = false; + // Whether autoclick is paused. + private boolean mPaused = false; + // Tracks the current corner position of the panel using an index into CORNER_ROTATION_ORDER + // array. This allows the panel to cycle through screen corners in a defined sequence when + // repositioned. + private int mCurrentCornerIndex = 0; + private final LinearLayout mLeftClickButton; private final LinearLayout mRightClickButton; private final LinearLayout mDoubleClickButton; private final LinearLayout mDragButton; private final LinearLayout mScrollButton; + private final LinearLayout mPauseButton; + private final LinearLayout mPositionButton; private LinearLayout mSelectedButton; + private final Drawable mPauseButtonDrawable; + private final Drawable mResumeButtonDrawable; + public AutoclickTypePanel( Context context, WindowManager windowManager, @@ -93,6 +134,11 @@ public class AutoclickTypePanel { mWindowManager = windowManager; mClickPanelController = clickPanelController; + mPauseButtonDrawable = mContext.getDrawable( + R.drawable.accessibility_autoclick_pause); + mResumeButtonDrawable = mContext.getDrawable( + R.drawable.accessibility_autoclick_resume); + mContentView = LayoutInflater.from(context) .inflate(R.layout.accessibility_autoclick_type_panel, null); @@ -104,6 +150,8 @@ public class AutoclickTypePanel { mContentView.findViewById(R.id.accessibility_autoclick_double_click_layout); mScrollButton = mContentView.findViewById(R.id.accessibility_autoclick_scroll_layout); mDragButton = mContentView.findViewById(R.id.accessibility_autoclick_drag_layout); + mPauseButton = mContentView.findViewById(R.id.accessibility_autoclick_pause_layout); + mPositionButton = mContentView.findViewById(R.id.accessibility_autoclick_position_layout); initializeButtonState(); } @@ -115,11 +163,8 @@ public class AutoclickTypePanel { v -> togglePanelExpansion(AUTOCLICK_TYPE_DOUBLE_CLICK)); mScrollButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_SCROLL)); mDragButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_DRAG)); - - // TODO(b/388872274): registers listener for pause button and allows users to pause/resume - // the autoclick. - // TODO(b/388847771): registers listener for position button and allows users to move the - // panel to a different position. + mPositionButton.setOnClickListener(v -> moveToNextCorner()); + mPauseButton.setOnClickListener(v -> togglePause()); // Initializes panel as collapsed state and only displays the left click button. hideAllClickTypeButtons(); @@ -175,6 +220,10 @@ public class AutoclickTypePanel { mWindowManager.removeView(mContentView); } + public boolean isPaused() { + return mPaused; + } + /** Toggles the panel expanded or collapsed state. */ private void togglePanelExpansion(@AutoclickType int clickType) { final LinearLayout button = getButtonFromClickType(clickType); @@ -196,6 +245,18 @@ public class AutoclickTypePanel { mExpanded = !mExpanded; } + private void togglePause() { + mPaused = !mPaused; + mClickPanelController.toggleAutoclickPause(mPaused); + + ImageButton imageButton = (ImageButton) mPauseButton.getChildAt(/* index= */ 0); + if (mPaused) { + imageButton.setImageDrawable(mResumeButtonDrawable); + } else { + imageButton.setImageDrawable(mPauseButtonDrawable); + } + } + /** Hide all buttons on the panel except pause and position buttons. */ private void hideAllClickTypeButtons() { mLeftClickButton.setVisibility(View.GONE); @@ -225,6 +286,46 @@ public class AutoclickTypePanel { }; } + /** Moves the panel to the next corner in clockwise direction. */ + private void moveToNextCorner() { + @Corner int nextCornerIndex = (mCurrentCornerIndex + 1) % CORNER_ROTATION_ORDER.length; + mCurrentCornerIndex = nextCornerIndex; + + // getLayoutParams() will update the panel position based on current corner. + WindowManager.LayoutParams params = getLayoutParams(); + mWindowManager.updateViewLayout(mContentView, params); + } + + private void setPanelPositionForCorner(WindowManager.LayoutParams params, @Corner int corner) { + // TODO(b/396402941): Replace hardcoded pixel values with proper dimension calculations, + // Current values are experimental and may not work correctly across different device + // resolutions and configurations. + switch (corner) { + case CORNER_BOTTOM_RIGHT: + params.gravity = Gravity.END | Gravity.BOTTOM; + params.x = 15; + params.y = 90; + break; + case CORNER_BOTTOM_LEFT: + params.gravity = Gravity.START | Gravity.BOTTOM; + params.x = 15; + params.y = 90; + break; + case CORNER_TOP_LEFT: + params.gravity = Gravity.START | Gravity.TOP; + params.x = 15; + params.y = 30; + break; + case CORNER_TOP_RIGHT: + params.gravity = Gravity.END | Gravity.TOP; + params.x = 15; + params.y = 30; + break; + default: + throw new IllegalArgumentException("Invalid corner: " + corner); + } + } + @VisibleForTesting boolean getExpansionStateForTesting() { return mExpanded; @@ -236,12 +337,19 @@ public class AutoclickTypePanel { return mContentView; } + @VisibleForTesting + @Corner + int getCurrentCornerIndexForTesting() { + return mCurrentCornerIndex; + } + /** * Retrieves the layout params for AutoclickIndicatorView, used when it's added to the Window * Manager. */ + @VisibleForTesting @NonNull - private WindowManager.LayoutParams getLayoutParams() { + WindowManager.LayoutParams getLayoutParams() { final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; @@ -254,11 +362,7 @@ public class AutoclickTypePanel { mContext.getString(R.string.accessibility_autoclick_type_settings_panel_title); layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; - // TODO(b/388847771): Compute position based on user interaction. - layoutParams.x = 15; - layoutParams.y = 90; - layoutParams.gravity = Gravity.END | Gravity.BOTTOM; - + setPanelPositionForCorner(layoutParams, mCurrentCornerIndex); return layoutParams; } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index d0ee7af1bbfb..5191fb5f51cb 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -20,6 +20,8 @@ import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_R import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE; import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR; +import static com.android.server.appfunctions.CallerValidator.CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_HAS_PERMISSION; +import static com.android.server.appfunctions.CallerValidator.CAN_EXECUTE_APP_FUNCTIONS_DENIED; import android.annotation.NonNull; import android.annotation.Nullable; @@ -236,30 +238,42 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { requestInternal.getCallingPackage(), targetPackageName, requestInternal.getClientRequest().getFunctionIdentifier()) - .thenAccept( - canExecute -> { - if (!canExecute) { - throw new SecurityException( + .thenCompose( + canExecuteResult -> { + if (canExecuteResult == CAN_EXECUTE_APP_FUNCTIONS_DENIED) { + return AndroidFuture.failedFuture(new SecurityException( "Caller does not have permission to execute the" - + " appfunction"); + + " appfunction")); } + return isAppFunctionEnabled( + requestInternal + .getClientRequest() + .getFunctionIdentifier(), + requestInternal + .getClientRequest() + .getTargetPackageName(), + getAppSearchManagerAsUser( + requestInternal.getUserHandle()), + THREAD_POOL_EXECUTOR) + .thenApply( + isEnabled -> { + if (!isEnabled) { + throw new DisabledAppFunctionException( + "The app function is disabled"); + } + return canExecuteResult; + }); }) - .thenCompose( - isEnabled -> - isAppFunctionEnabled( - requestInternal.getClientRequest().getFunctionIdentifier(), - requestInternal.getClientRequest().getTargetPackageName(), - getAppSearchManagerAsUser(requestInternal.getUserHandle()), - THREAD_POOL_EXECUTOR)) .thenAccept( - isEnabled -> { - if (!isEnabled) { - throw new DisabledAppFunctionException( - "The app function is disabled"); + canExecuteResult -> { + int bindFlags = Context.BIND_AUTO_CREATE; + if (canExecuteResult + == CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_HAS_PERMISSION) { + // If the caller doesn't have the permission, do not use + // BIND_FOREGROUND_SERVICE to avoid it raising its process state by + // calling its own AppFunctions. + bindFlags |= Context.BIND_FOREGROUND_SERVICE; } - }) - .thenAccept( - unused -> { Intent serviceIntent = mInternalServiceHelper.resolveAppFunctionService( targetPackageName, targetUser); @@ -294,8 +308,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { targetUser, localCancelTransport, safeExecuteAppFunctionCallback, - /* bindFlags= */ Context.BIND_AUTO_CREATE - | Context.BIND_FOREGROUND_SERVICE, + bindFlags, callerBinder, callingUid); }) diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java index 98ef974b9443..c8038a4e56df 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java +++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java @@ -17,12 +17,16 @@ package com.android.server.appfunctions; import android.Manifest; +import android.annotation.IntDef; import android.annotation.NonNull; import android.os.UserHandle; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Interface for validating that the caller has the correct privilege to call an AppFunctionManager * API. @@ -70,7 +74,8 @@ public interface CallerValidator { * @param functionId The id of the app function to execute. * @return Whether the caller can execute the specified app function. */ - AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction( + @CanExecuteAppFunctionResult + AndroidFuture<Integer> verifyCallerCanExecuteAppFunction( int callingUid, int callingPid, @NonNull UserHandle targetUser, @@ -78,6 +83,31 @@ public interface CallerValidator { @NonNull String targetPackageName, @NonNull String functionId); + @IntDef( + prefix = {"CAN_EXECUTE_APP_FUNCTIONS_"}, + value = { + CAN_EXECUTE_APP_FUNCTIONS_DENIED, + CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_SAME_PACKAGE, + CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_HAS_PERMISSION, + }) + @Retention(RetentionPolicy.SOURCE) + @interface CanExecuteAppFunctionResult {} + + /** Callers are not allowed to execute app functions. */ + int CAN_EXECUTE_APP_FUNCTIONS_DENIED = 0; + + /** + * Callers can execute app functions because they are calling app functions from the same + * package. + */ + int CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_SAME_PACKAGE = 1; + + /** + * Callers can execute app functions because they have the necessary permission. + * This case also applies when a caller with the permission invokes their own app functions. + */ + int CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_HAS_PERMISSION = 2; + /** * Checks if the app function policy is allowed. * diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java index fe163d77c4fc..3f8b2e3316dc 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java @@ -16,24 +16,12 @@ package com.android.server.appfunctions; -import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB; -import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_NAMESPACE; -import static android.app.appfunctions.AppFunctionStaticMetadataHelper.getDocumentIdForAppFunction; - -import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR; - import android.Manifest; import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager.AppFunctionsPolicy; -import android.app.appsearch.AppSearchBatchResult; -import android.app.appsearch.AppSearchManager; -import android.app.appsearch.AppSearchManager.SearchContext; -import android.app.appsearch.AppSearchResult; -import android.app.appsearch.GenericDocument; -import android.app.appsearch.GetByDocumentIdRequest; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; @@ -84,64 +72,25 @@ class CallerValidatorImpl implements CallerValidator { @Override @RequiresPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS) - public AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction( + @CanExecuteAppFunctionResult + public AndroidFuture<Integer> verifyCallerCanExecuteAppFunction( int callingUid, int callingPid, @NonNull UserHandle targetUser, @NonNull String callerPackageName, @NonNull String targetPackageName, @NonNull String functionId) { - if (callerPackageName.equals(targetPackageName)) { - return AndroidFuture.completedFuture(true); - } - boolean hasExecutionPermission = mContext.checkPermission( - Manifest.permission.EXECUTE_APP_FUNCTIONS, callingPid, callingUid) + Manifest.permission.EXECUTE_APP_FUNCTIONS, callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; - - if (!hasExecutionPermission) { - return AndroidFuture.completedFuture(false); + if (hasExecutionPermission) { + return AndroidFuture.completedFuture(CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_HAS_PERMISSION); } - - FutureAppSearchSession futureAppSearchSession = - new FutureAppSearchSessionImpl( - Objects.requireNonNull( - mContext.createContextAsUser(targetUser, 0) - .getSystemService(AppSearchManager.class)), - THREAD_POOL_EXECUTOR, - new SearchContext.Builder(APP_FUNCTION_STATIC_METADATA_DB).build()); - - String documentId = getDocumentIdForAppFunction(targetPackageName, functionId); - - return futureAppSearchSession - .getByDocumentId( - new GetByDocumentIdRequest.Builder(APP_FUNCTION_STATIC_NAMESPACE) - .addIds(documentId) - .build()) - .thenApply( - batchResult -> getGenericDocumentFromBatchResult(batchResult, documentId)) - // At this point, already checked the app has the permission. - .thenApply(document -> true) - .whenComplete( - (result, throwable) -> { - futureAppSearchSession.close(); - }); - } - - private static GenericDocument getGenericDocumentFromBatchResult( - AppSearchBatchResult<String, GenericDocument> result, String documentId) { - if (result.isSuccess()) { - return result.getSuccesses().get(documentId); + if (callerPackageName.equals(targetPackageName)) { + return AndroidFuture.completedFuture(CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_SAME_PACKAGE); } - - AppSearchResult<GenericDocument> failedResult = result.getFailures().get(documentId); - throw new AppSearchException( - failedResult.getResultCode(), - "Unable to retrieve document with id: " - + documentId - + " due to " - + failedResult.getErrorMessage()); + return AndroidFuture.completedFuture(CAN_EXECUTE_APP_FUNCTIONS_DENIED); } @Override diff --git a/services/appwidget/Android.bp b/services/appwidget/Android.bp index 8119073fdf7f..9548afe398b1 100644 --- a/services/appwidget/Android.bp +++ b/services/appwidget/Android.bp @@ -17,6 +17,12 @@ filegroup { java_library_static { name: "services.appwidget", defaults: ["platform_service_defaults"], - srcs: [":services.appwidget-sources"], - libs: ["services.core"], + srcs: [ + ":services.appwidget-sources", + ":statslog-framework-java-gen", + ], + libs: [ + "androidx.annotation_annotation", + "services.core", + ], } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index e0f2939a2083..74a87ed92f52 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -50,6 +50,7 @@ import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.KeyguardManager; import android.app.PendingIntent; +import android.app.StatsManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DevicePolicyManagerInternal.OnCrossProfileWidgetProvidersChangeListener; import android.app.usage.Flags; @@ -124,6 +125,7 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.SparseLongArray; +import android.util.StatsEvent; import android.util.TypedValue; import android.util.Xml; import android.util.proto.ProtoInputStream; @@ -145,6 +147,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.IRemoteViewsFactory; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -433,6 +436,44 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mAppOpsManagerInternal = LocalServices.getService(AppOpsManagerInternal.class); mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class); + registerPullCallbacks(); + } + + /** + * Register callbacks for pull atoms. + */ + private void registerPullCallbacks() { + final StatsManager manager = mContext.getSystemService(StatsManager.class); + manager.setPullAtomCallback(FrameworkStatsLog.WIDGET_MEMORY_STATS, + new StatsManager.PullAtomMetadata.Builder().build(), + new HandlerExecutor(mCallbackHandler), this::onPullAtom); + } + + /** + * Callback from StatsManager to log events indicated by the atomTag. This function will add + * the relevant events to the data list. + * + * @return PULL_SUCCESS if the pull was successful and events should be used, else PULL_SKIP. + */ + private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) { + if (atomTag == FrameworkStatsLog.WIDGET_MEMORY_STATS) { + synchronized (mLock) { + for (Widget widget : mWidgets) { + if (widget.views != null) { + final int uid = widget.provider.id.uid; + final int appWidgetId = widget.appWidgetId; + final long bitmapMemoryUsage = + widget.views.estimateTotalBitmapMemoryUsage(); + StatsEvent event = FrameworkStatsLog.buildStatsEvent( + FrameworkStatsLog.WIDGET_MEMORY_STATS, uid, appWidgetId, + bitmapMemoryUsage); + data.add(event); + } + } + } + return StatsManager.PULL_SUCCESS; + } + return StatsManager.PULL_SKIP; } /** diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 05301fdd8385..4f56483f487e 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -31,6 +31,8 @@ import static android.os.UserHandle.getCallingUserId; import static com.android.internal.util.CollectionUtils.any; import static com.android.internal.util.Preconditions.checkState; +import static com.android.server.companion.association.DisassociationProcessor.REASON_API; +import static com.android.server.companion.association.DisassociationProcessor.REASON_PKG_DATA_CLEARED; import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature; import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; @@ -250,7 +252,7 @@ public class CompanionDeviceManagerService extends SystemService { + packageName + "]. Cleaning up CDM data..."); for (AssociationInfo association : associationsForPackage) { - mDisassociationProcessor.disassociate(association.getId()); + mDisassociationProcessor.disassociate(association.getId(), REASON_PKG_DATA_CLEARED); } mCompanionAppBinder.onPackageChanged(userId); @@ -426,7 +428,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void disassociate(int associationId) { - mDisassociationProcessor.disassociate(associationId); + mDisassociationProcessor.disassociate(associationId, REASON_API); } @Override diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index e7d1460aa66a..c5ac7c31b5c3 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -18,6 +18,8 @@ package com.android.server.companion; import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC; +import static com.android.server.companion.association.DisassociationProcessor.REASON_SHELL; + import android.companion.AssociationInfo; import android.companion.ContextSyncMessage; import android.companion.Flags; @@ -122,7 +124,7 @@ class CompanionDeviceShellCommand extends ShellCommand { if (association == null) { out.println("Association doesn't exist."); } else { - mDisassociationProcessor.disassociate(association.getId()); + mDisassociationProcessor.disassociate(association.getId(), REASON_SHELL); } } break; @@ -132,7 +134,7 @@ class CompanionDeviceShellCommand extends ShellCommand { final List<AssociationInfo> userAssociations = mAssociationStore.getAssociationsByUser(userId); for (AssociationInfo association : userAssociations) { - mDisassociationProcessor.disassociate(association.getId()); + mDisassociationProcessor.disassociate(association.getId(), REASON_SHELL); } } break; diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java index f2d019bde703..ce7dcd0fa1d4 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java +++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java @@ -58,6 +58,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; @@ -164,6 +165,7 @@ public final class AssociationDiskStore { private static final String FILE_NAME_LEGACY = "companion_device_manager_associations.xml"; private static final String FILE_NAME = "companion_device_manager.xml"; + private static final String FILE_NAME_LAST_REMOVED_ASSOCIATION = "last_removed_association.txt"; private static final String XML_TAG_STATE = "state"; private static final String XML_TAG_ASSOCIATIONS = "associations"; @@ -268,6 +270,46 @@ public final class AssociationDiskStore { } } + /** + * Read the last removed association from disk. + */ + public String readLastRemovedAssociation(@UserIdInt int userId) { + final AtomicFile file = createStorageFileForUser( + userId, FILE_NAME_LAST_REMOVED_ASSOCIATION); + StringBuilder sb = new StringBuilder(); + int c; + try (FileInputStream fis = file.openRead()) { + while ((c = fis.read()) != -1) { + sb.append((char) c); + } + fis.close(); + return sb.toString(); + } catch (FileNotFoundException e) { + Slog.e(TAG, "File " + file + " for user=" + userId + " doesn't exist."); + return null; + } catch (IOException e) { + Slog.e(TAG, "Can't read file " + file + " for user=" + userId); + return null; + } + } + + /** + * Write the last removed association to disk. + */ + public void writeLastRemovedAssociation(AssociationInfo association, String reason) { + Slog.i(TAG, "Writing last removed association=" + association.getId() + " to disk..."); + + final AtomicFile file = createStorageFileForUser( + association.getUserId(), FILE_NAME_LAST_REMOVED_ASSOCIATION); + writeToFileSafely(file, out -> { + out.write(String.valueOf(System.currentTimeMillis()).getBytes()); + out.write(' '); + out.write(reason.getBytes()); + out.write(' '); + out.write(association.toString().getBytes()); + }); + } + @NonNull private static Associations readAssociationsFromFile(@UserIdInt int userId, @NonNull AtomicFile file, @NonNull String rootTag) { diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java index 757abd927ac8..f70c434e6b46 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationStore.java +++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java @@ -276,7 +276,7 @@ public class AssociationStore { /** * Remove an association. */ - public void removeAssociation(int id) { + public void removeAssociation(int id, String reason) { Slog.i(TAG, "Removing association id=[" + id + "]..."); final AssociationInfo association; @@ -291,6 +291,8 @@ public class AssociationStore { writeCacheToDisk(association.getUserId()); + mDiskStore.writeLastRemovedAssociation(association, reason); + Slog.i(TAG, "Done removing association."); } @@ -525,6 +527,14 @@ public class AssociationStore { out.append(" ").append(a.toString()).append('\n'); } } + + out.append("Last Removed Association:\n"); + for (UserInfo user : mUserManager.getAliveUsers()) { + String lastRemovedAssociation = mDiskStore.readLastRemovedAssociation(user.id); + if (lastRemovedAssociation != null) { + out.append(" ").append(lastRemovedAssociation).append('\n'); + } + } } private void broadcastChange(@ChangeType int changeType, AssociationInfo association) { diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java index 150e8da5f614..248056f32a4f 100644 --- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java +++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java @@ -47,6 +47,13 @@ import com.android.server.companion.transport.CompanionTransportManager; @SuppressLint("LongLogTag") public class DisassociationProcessor { + public static final String REASON_REVOKED = "revoked"; + public static final String REASON_SELF_IDLE = "self-idle"; + public static final String REASON_SHELL = "shell"; + public static final String REASON_LEGACY = "legacy"; + public static final String REASON_API = "api"; + public static final String REASON_PKG_DATA_CLEARED = "pkg-data-cleared"; + private static final String TAG = "CDM_DisassociationProcessor"; private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW = @@ -94,7 +101,7 @@ public class DisassociationProcessor { * Disassociate an association by id. */ // TODO: also revoke notification access - public void disassociate(int id) { + public void disassociate(int id, String reason) { Slog.i(TAG, "Disassociating id=[" + id + "]..."); final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id); @@ -126,7 +133,7 @@ public class DisassociationProcessor { // Association cleanup. mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id); - mAssociationStore.removeAssociation(association.getId()); + mAssociationStore.removeAssociation(association.getId(), reason); // If role is not in use by other associations, revoke the role. // Do not need to remove the system role since it was pre-granted by the system. @@ -151,7 +158,7 @@ public class DisassociationProcessor { } /** - * @deprecated Use {@link #disassociate(int)} instead. + * @deprecated Use {@link #disassociate(int, String)} instead. */ @Deprecated public void disassociate(int userId, String packageName, String macAddress) { @@ -165,7 +172,7 @@ public class DisassociationProcessor { mAssociationStore.getAssociationWithCallerChecks(association.getId()); - disassociate(association.getId()); + disassociate(association.getId(), REASON_LEGACY); } @SuppressLint("MissingPermission") @@ -223,7 +230,7 @@ public class DisassociationProcessor { Slog.i(TAG, "Removing inactive self-managed association=[" + association.toShortString() + "]."); - disassociate(id); + disassociate(id, REASON_SELF_IDLE); } } @@ -234,7 +241,7 @@ public class DisassociationProcessor { * * Lastly remove the role holder for the revoked associations for the same packages. * - * @see #disassociate(int) + * @see #disassociate(int, String) */ private class OnPackageVisibilityChangeListener implements ActivityManager.OnUidImportanceListener { @@ -260,7 +267,7 @@ public class DisassociationProcessor { int userId = UserHandle.getUserId(uid); for (AssociationInfo association : mAssociationStore.getRevokedAssociations(userId, packageName)) { - disassociate(association.getId()); + disassociate(association.getId(), REASON_REVOKED); } if (mAssociationStore.getRevokedAssociations().isEmpty()) { diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index f03e8c713228..28efdfc01ee2 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -163,6 +163,16 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub */ private static final long PENDING_TRAMPOLINE_TIMEOUT_MS = 5000; + /** + * Global lock for this Virtual Device. + * + * Never call outside this class while holding this lock. A number of other system services like + * WindowManager, DisplayManager, etc. call into this device to get device-specific information, + * while holding their own global locks. + * + * Making a call to another service while holding this lock creates lock order inversion and + * will potentially cause a deadlock. + */ private final Object mVirtualDeviceLock = new Object(); private final int mBaseVirtualDisplayFlags; @@ -179,14 +189,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final int mDeviceId; @Nullable private final String mPersistentDeviceId; - // Thou shall not hold the mVirtualDeviceLock over the mInputController calls. - // Holding the lock can lead to lock inversion with GlobalWindowManagerLock. - // 1. After display is created the window manager calls into VDM during construction - // of display specific context to fetch device id corresponding to the display. - // mVirtualDeviceLock will be held while this is done. - // 2. InputController interactions result in calls to DisplayManager (to set IME, - // possibly more indirect calls), and those attempt to lock GlobalWindowManagerLock which - // creates lock inversion. private final InputController mInputController; private final SensorController mSensorController; private final CameraAccessController mCameraAccessController; @@ -205,19 +207,23 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final DisplayManagerGlobal mDisplayManager; private final DisplayManagerInternal mDisplayManagerInternal; private final PowerManager mPowerManager; - @GuardedBy("mVirtualDeviceLock") + @GuardedBy("mIntentInterceptors") private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>(); @NonNull private final Consumer<ArraySet<Integer>> mRunningAppsChangedCallback; + // The default setting for showing the pointer on new displays. @GuardedBy("mVirtualDeviceLock") private boolean mDefaultShowPointerIcon = true; @GuardedBy("mVirtualDeviceLock") @Nullable private LocaleList mLocaleList = null; - @GuardedBy("mVirtualDeviceLock") + + // Lock for power operations for this virtual device that allow calling PowerManager. + private final Object mPowerLock = new Object(); + @GuardedBy("mPowerLock") private boolean mLockdownActive = false; - @GuardedBy("mVirtualDeviceLock") + @GuardedBy("mPowerLock") private boolean mRequestedToBeAwake = true; @NonNull @@ -334,7 +340,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub */ @Override public boolean shouldInterceptIntent(@NonNull Intent intent) { - synchronized (mVirtualDeviceLock) { + synchronized (mIntentInterceptors) { boolean hasInterceptedIntent = false; for (Map.Entry<IBinder, IntentFilter> interceptor : mIntentInterceptors.entrySet()) { @@ -500,7 +506,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } void onLockdownChanged(boolean lockdownActive) { - synchronized (mVirtualDeviceLock) { + synchronized (mPowerLock) { if (lockdownActive != mLockdownActive) { mLockdownActive = lockdownActive; if (mLockdownActive) { @@ -610,7 +616,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @Override // Binder call public void goToSleep() { checkCallerIsDeviceOwner(); - synchronized (mVirtualDeviceLock) { + synchronized (mPowerLock) { mRequestedToBeAwake = false; } final long ident = Binder.clearCallingIdentity(); @@ -624,7 +630,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @Override // Binder call public void wakeUp() { checkCallerIsDeviceOwner(); - synchronized (mVirtualDeviceLock) { + synchronized (mPowerLock) { mRequestedToBeAwake = true; if (mLockdownActive) { Slog.w(TAG, "Cannot wake up device during lockdown."); @@ -850,8 +856,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub checkDisplayOwnedByVirtualDeviceLocked(displayId); if (mVirtualAudioController == null) { mVirtualAudioController = new VirtualAudioController(mContext, mAttributionSource); - GenericWindowPolicyController gwpc = mVirtualDisplays.get( - displayId).getWindowPolicyController(); + GenericWindowPolicyController gwpc = + mVirtualDisplays.get(displayId).getWindowPolicyController(); mVirtualAudioController.startListening(gwpc, routingCallback, configChangedCallback); } @@ -1293,7 +1299,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub checkCallerIsDeviceOwner(); Objects.requireNonNull(intentInterceptor); Objects.requireNonNull(filter); - synchronized (mVirtualDeviceLock) { + synchronized (mIntentInterceptors) { mIntentInterceptors.put(intentInterceptor.asBinder(), filter); } } @@ -1303,7 +1309,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @NonNull IVirtualDeviceIntentInterceptor intentInterceptor) { checkCallerIsDeviceOwner(); Objects.requireNonNull(intentInterceptor); - synchronized (mVirtualDeviceLock) { + synchronized (mIntentInterceptors) { mIntentInterceptors.remove(intentInterceptor.asBinder()); } } @@ -1653,6 +1659,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub void goToSleepInternal(@PowerManager.GoToSleepReason int reason) { final long now = SystemClock.uptimeMillis(); + IntArray displayIds = new IntArray(); synchronized (mVirtualDeviceLock) { for (int i = 0; i < mVirtualDisplays.size(); i++) { VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i); @@ -1660,13 +1667,17 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub continue; } int displayId = mVirtualDisplays.keyAt(i); - mPowerManager.goToSleep(displayId, now, reason, /* flags= */ 0); + displayIds.add(displayId); } } + for (int i = 0; i < displayIds.size(); ++i) { + mPowerManager.goToSleep(displayIds.get(i), now, reason, /* flags= */ 0); + } } void wakeUpInternal(@PowerManager.WakeReason int reason, String details) { final long now = SystemClock.uptimeMillis(); + IntArray displayIds = new IntArray(); synchronized (mVirtualDeviceLock) { for (int i = 0; i < mVirtualDisplays.size(); i++) { VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i); @@ -1674,9 +1685,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub continue; } int displayId = mVirtualDisplays.keyAt(i); - mPowerManager.wakeUp(now, reason, details, displayId); + displayIds.add(displayId); } } + for (int i = 0; i < displayIds.size(); ++i) { + mPowerManager.wakeUp(now, reason, details, displayIds.get(i)); + } } /** diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index ff82ca00b840..93b4de856463 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -117,7 +117,18 @@ public class VirtualDeviceManagerService extends SystemService { */ static final int CDM_ASSOCIATION_ID_NONE = 0; + /** + * Global VDM lock. + * + * Never call outside this class while holding this lock. A number of other system services like + * WindowManager, DisplayManager, etc. call into VDM to get device-specific information, while + * holding their own global locks. + * + * Making a call to another service while holding this lock creates lock order inversion and + * will potentially cause a deadlock. + */ private final Object mVirtualDeviceManagerLock = new Object(); + private final VirtualDeviceManagerImpl mImpl; private final VirtualDeviceManagerNativeImpl mNativeImpl; private final VirtualDeviceManagerInternal mLocalService; @@ -235,10 +246,9 @@ public class VirtualDeviceManagerService extends SystemService { // Called when the global lockdown state changes, i.e. lockdown is considered active if any user // is in lockdown mode, and inactive if no users are in lockdown mode. void onLockdownChanged(boolean lockdownActive) { - synchronized (mVirtualDeviceManagerLock) { - for (int i = 0; i < mVirtualDevices.size(); i++) { - mVirtualDevices.valueAt(i).onLockdownChanged(lockdownActive); - } + ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot(); + for (int i = 0; i < virtualDevicesSnapshot.size(); i++) { + virtualDevicesSnapshot.get(i).onLockdownChanged(lockdownActive); } } @@ -264,16 +274,14 @@ public class VirtualDeviceManagerService extends SystemService { return null; } int userId = userHandle.getIdentifier(); - synchronized (mVirtualDeviceManagerLock) { - for (int i = 0; i < mVirtualDevices.size(); i++) { - final CameraAccessController cameraAccessController = - mVirtualDevices.valueAt(i).getCameraAccessController(); - if (cameraAccessController.getUserId() == userId) { - return cameraAccessController; - } + ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot(); + for (int i = 0; i < virtualDevicesSnapshot.size(); i++) { + final CameraAccessController cameraAccessController = + virtualDevicesSnapshot.get(i).getCameraAccessController(); + if (cameraAccessController.getUserId() == userId) { + return cameraAccessController; } - } - Context userContext = getContext().createContextAsUser(userHandle, 0); + } Context userContext = getContext().createContextAsUser(userHandle, 0); return new CameraAccessController(userContext, mLocalService, this::onCameraAccessBlocked); } @@ -520,11 +528,10 @@ public class VirtualDeviceManagerService extends SystemService { @Override // Binder call public List<VirtualDevice> getVirtualDevices() { List<VirtualDevice> virtualDevices = new ArrayList<>(); - synchronized (mVirtualDeviceManagerLock) { - for (int i = 0; i < mVirtualDevices.size(); i++) { - final VirtualDeviceImpl device = mVirtualDevices.valueAt(i); - virtualDevices.add(device.getPublicVirtualDeviceObject()); - } + ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot(); + for (int i = 0; i < virtualDevicesSnapshot.size(); i++) { + VirtualDeviceImpl device = virtualDevicesSnapshot.get(i); + virtualDevices.add(device.getPublicVirtualDeviceObject()); } return virtualDevices; } @@ -834,15 +841,17 @@ public class VirtualDeviceManagerService extends SystemService { @Nullable public LocaleList getPreferredLocaleListForUid(int uid) { // TODO: b/263188984 support the case where an app is running on multiple VDs + VirtualDeviceImpl virtualDevice = null; synchronized (mVirtualDeviceManagerLock) { for (int i = 0; i < mAppsOnVirtualDevices.size(); i++) { if (mAppsOnVirtualDevices.valueAt(i).contains(uid)) { int deviceId = mAppsOnVirtualDevices.keyAt(i); - return mVirtualDevices.get(deviceId).getDeviceLocaleList(); + virtualDevice = mVirtualDevices.get(deviceId); + break; } } } - return null; + return virtualDevice == null ? null : virtualDevice.getDeviceLocaleList(); } @Override @@ -882,6 +891,11 @@ public class VirtualDeviceManagerService extends SystemService { } @Override + public VirtualDevice getVirtualDevice(int deviceId) { + return mImpl.getVirtualDevice(deviceId); + } + + @Override public long getDimDurationMillisForDeviceId(int deviceId) { VirtualDeviceImpl virtualDevice = getVirtualDeviceForId(deviceId); return virtualDevice == null ? -1 : virtualDevice.getDimDurationMillis(); diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java index 89c9d690a82c..7eb7072520de 100644 --- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java +++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java @@ -34,9 +34,10 @@ import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUC import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.ActivityOptions; import android.app.AppOpsManager; -import android.app.admin.DevicePolicyManagerInternal; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; import android.app.contextualsearch.CallbackToken; @@ -65,11 +66,11 @@ import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.SystemClock; +import android.os.UserManager; import android.provider.Settings; import android.util.Log; import android.util.Slog; import android.view.IWindowManager; -import android.window.ScreenCapture; import android.window.ScreenCapture.ScreenshotHardwareBuffer; import com.android.internal.R; @@ -86,7 +87,6 @@ import java.io.FileDescriptor; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Set; public class ContextualSearchManagerService extends SystemService { private static final String TAG = ContextualSearchManagerService.class.getSimpleName(); @@ -95,14 +95,25 @@ public class ContextualSearchManagerService extends SystemService { private static final int MSG_INVALIDATE_TOKEN = 1; private static final int MAX_TOKEN_VALID_DURATION_MS = 1_000 * 60 * 10; // 10 minutes + /** + * Below are internal entrypoints not supported by the + * {@link ContextualSearchManager#startContextualSearch(int entrypoint)} method. + * + * <p>These values should be negative to avoid conflicting with the system entrypoints. + */ + + /** Entrypoint to be used when a foreground app invokes Contextual Search. */ + private static final int INTERNAL_ENTRYPOINT_APP = -1; + private static final boolean DEBUG = false; private final Context mContext; + private final ActivityManagerInternal mActivityManagerInternal; private final ActivityTaskManagerInternal mAtmInternal; private final PackageManagerInternal mPackageManager; private final WindowManagerInternal mWmInternal; - private final DevicePolicyManagerInternal mDpmInternal; private final AudioManager mAudioManager; + private final UserManager mUserManager; private final Object mLock = new Object(); private final AssistDataRequester mAssistDataRequester; @@ -162,13 +173,15 @@ public class ContextualSearchManagerService extends SystemService { super(context); if (DEBUG) Log.d(TAG, "ContextualSearchManagerService created"); mContext = context; + mActivityManagerInternal = Objects.requireNonNull( + LocalServices.getService(ActivityManagerInternal.class)); mAtmInternal = Objects.requireNonNull( LocalServices.getService(ActivityTaskManagerInternal.class)); mPackageManager = LocalServices.getService(PackageManagerInternal.class); mAudioManager = context.getSystemService(AudioManager.class); + mUserManager = context.getSystemService(UserManager.class); mWmInternal = Objects.requireNonNull(LocalServices.getService(WindowManagerInternal.class)); - mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class); mAssistDataRequester = new AssistDataRequester( mContext, IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE)), @@ -295,6 +308,11 @@ public class ContextualSearchManagerService extends SystemService { } } + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS, + android.Manifest.permission.QUERY_USERS + }) private Intent getContextualSearchIntent(int entrypoint, int userId, CallbackToken mToken) { final Intent launchIntent = getResolvedLaunchIntent(userId); if (launchIntent == null) { @@ -325,8 +343,7 @@ public class ContextualSearchManagerService extends SystemService { visiblePackageNames.add(record.getComponentName().getPackageName()); activityTokens.add(record.getActivityToken()); } - if (mDpmInternal != null - && mDpmInternal.isUserOrganizationManaged(record.getUserId())) { + if (mUserManager.isManagedProfile(record.getUserId())) { isManagedProfileVisible = true; } } @@ -348,7 +365,7 @@ public class ContextualSearchManagerService extends SystemService { } } final ScreenshotHardwareBuffer shb = mWmInternal.takeContextualSearchScreenshot( - (Flags.contextualSearchWindowLayer() ? csUid : -1)); + (Flags.contextualSearchPreventSelfCapture() ? csUid : -1)); final Bitmap bm = shb != null ? shb.asBitmap() : null; // Now that everything is fetched, putting it in the launchIntent. if (bm != null) { @@ -391,6 +408,20 @@ public class ContextualSearchManagerService extends SystemService { } } + private void enforceForegroundApp(@NonNull final String func) { + final int callingUid = Binder.getCallingUid(); + final String callingPackage = mPackageManager.getNameForUid(Binder.getCallingUid()); + if (mActivityManagerInternal.getUidProcessState(callingUid) + > ActivityManager.PROCESS_STATE_TOP) { + // The calling process must be displaying an activity in foreground to + // trigger contextual search. + String msg = "Permission Denial: Cannot call " + func + " from pid=" + + Binder.getCallingPid() + ", uid=" + callingUid + + ", package=" + callingPackage + " without a foreground activity."; + throw new SecurityException(msg); + } + } + private void enforceOverridingPermission(@NonNull final String func) { if (!(Binder.getCallingUid() == Process.SHELL_UID || Binder.getCallingUid() == Process.ROOT_UID @@ -448,27 +479,44 @@ public class ContextualSearchManagerService extends SystemService { } @Override + public void startContextualSearchForForegroundApp() { + synchronized (this) { + if (DEBUG) { + Log.d(TAG, "Starting contextual search from: " + + mPackageManager.getNameForUid(Binder.getCallingUid())); + } + enforceForegroundApp("startContextualSearchForForegroundApp"); + startContextualSearchInternal(INTERNAL_ENTRYPOINT_APP); + } + } + + @Override public void startContextualSearch(int entrypoint) { synchronized (this) { if (DEBUG) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint); enforcePermission("startContextualSearch"); - final int callingUserId = Binder.getCallingUserHandle().getIdentifier(); + startContextualSearchInternal(entrypoint); + } + } - mAssistDataRequester.cancel(); - // Creates a new CallbackToken at mToken and an expiration handler. - issueToken(); - // We get the launch intent with the system server's identity because the system - // server has READ_FRAME_BUFFER permission to get the screenshot and because only - // the system server can invoke non-exported activities. - Binder.withCleanCallingIdentity(() -> { - Intent launchIntent = - getContextualSearchIntent(entrypoint, callingUserId, mToken); - if (launchIntent != null) { - int result = invokeContextualSearchIntent(launchIntent, callingUserId); - if (DEBUG) Log.d(TAG, "Launch result: " + result); + private void startContextualSearchInternal(int entrypoint) { + final int callingUserId = Binder.getCallingUserHandle().getIdentifier(); + mAssistDataRequester.cancel(); + // Creates a new CallbackToken at mToken and an expiration handler. + issueToken(); + // We get the launch intent with the system server's identity because the system + // server has READ_FRAME_BUFFER permission to get the screenshot and because only + // the system server can invoke non-exported activities. + Binder.withCleanCallingIdentity(() -> { + Intent launchIntent = getContextualSearchIntent(entrypoint, callingUserId, mToken); + if (launchIntent != null) { + int result = invokeContextualSearchIntent(launchIntent, callingUserId); + if (DEBUG) { + Log.d(TAG, "Launch intent: " + launchIntent); + Log.d(TAG, "Launch result: " + result); } - }); - } + } + }); } @Override @@ -501,7 +549,7 @@ public class ContextualSearchManagerService extends SystemService { Binder.withCleanCallingIdentity(() -> { final ScreenshotHardwareBuffer shb = mWmInternal.takeContextualSearchScreenshot( - (Flags.contextualSearchWindowLayer() ? callingUid : -1)); + (Flags.contextualSearchPreventSelfCapture() ? callingUid : -1)); final Bitmap bm = shb != null ? shb.asBitmap() : null; if (bm != null) { bundle.putParcelable(ContextualSearchManager.EXTRA_SCREENSHOT, diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java index 04edb5756599..cd632e638281 100644 --- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java +++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java @@ -31,6 +31,8 @@ import static com.android.server.wm.WindowManagerInternal.OnWindowRemovedListene import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.role.RoleManager; +import android.companion.AssociationRequest; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; @@ -81,6 +83,8 @@ public final class SensitiveContentProtectionManagerService extends SystemServic private PackageManagerInternal mPackageManagerInternal; + private RoleManager mRoleManager; + @Nullable private WindowManagerInternal mWindowManager; @@ -225,7 +229,8 @@ public final class SensitiveContentProtectionManagerService extends SystemServic } @Override - public void onStart() {} + public void onStart() { + } @Override public void onBootPhase(int phase) { @@ -237,6 +242,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic init(getContext().getSystemService(MediaProjectionManager.class), LocalServices.getService(WindowManagerInternal.class), LocalServices.getService(PackageManagerInternal.class), + getContext().getSystemService(RoleManager.class), getExemptedPackages() ); if (sensitiveContentAppProtection()) { @@ -247,7 +253,8 @@ public final class SensitiveContentProtectionManagerService extends SystemServic @VisibleForTesting void init(MediaProjectionManager projectionManager, WindowManagerInternal windowManager, - PackageManagerInternal packageManagerInternal, ArraySet<String> exemptedPackages) { + PackageManagerInternal packageManagerInternal, RoleManager roleManager, + ArraySet<String> exemptedPackages) { if (DEBUG) Log.d(TAG, "init"); Objects.requireNonNull(projectionManager); @@ -256,6 +263,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic mProjectionManager = projectionManager; mWindowManager = windowManager; mPackageManagerInternal = packageManagerInternal; + mRoleManager = roleManager; mExemptedPackages = exemptedPackages; // TODO(b/317250444): use MediaProjectionManagerService directly, reduces unnecessary @@ -312,8 +320,10 @@ public final class SensitiveContentProtectionManagerService extends SystemServic boolean isPackageExempted = (mExemptedPackages != null && mExemptedPackages.contains( projectionInfo.getPackageName())) || canRecordSensitiveContent(projectionInfo.getPackageName()) + || holdsAppStreamingRole(projectionInfo.getPackageName(), + projectionInfo.getUserHandle()) || isAutofillServiceRecorderPackage(projectionInfo.getUserHandle().getIdentifier(), - projectionInfo.getPackageName()); + projectionInfo.getPackageName()); // TODO(b/324447419): move GlobalSettings lookup to background thread boolean isFeatureDisabled = Settings.Global.getInt(getContext().getContentResolver(), DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0; @@ -348,6 +358,11 @@ public final class SensitiveContentProtectionManagerService extends SystemServic } } + private boolean holdsAppStreamingRole(String packageName, UserHandle userHandle) { + return mRoleManager.getRoleHoldersAsUser( + AssociationRequest.DEVICE_PROFILE_APP_STREAMING, userHandle).contains(packageName); + } + private void onProjectionEnd() { synchronized (mSensitiveContentProtectionLock) { mProjectionActive = false; diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 4976a63b016b..8eda17698b9b 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -18,7 +18,6 @@ package com.android.server; import static android.app.Flags.enableCurrentModeTypeBinderCache; import static android.app.Flags.enableNightModeBinderCache; -import static android.app.Flags.modesApi; import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE; import static android.app.UiModeManager.DEFAULT_PRIORITY; import static android.app.UiModeManager.FORCE_INVERT_TYPE_DARK; @@ -2208,14 +2207,12 @@ final class UiModeManagerService extends SystemService { appliedOverrides = true; } - if (modesApi()) { - // Computes final night mode values based on Attention Mode. - mComputedNightMode = switch (mAttentionModeThemeOverlay) { - case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT) -> true; - case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY) -> false; - default -> newComputedValue; // case OFF - }; - } + // Computes final night mode values based on Attention Mode. + mComputedNightMode = switch (mAttentionModeThemeOverlay) { + case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT) -> true; + case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY) -> false; + default -> newComputedValue; // case OFF + }; if (appliedOverrides) { return; diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index cce29592d912..125824c3f37e 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -618,7 +618,7 @@ public final class ActiveServices { Slog.i(TAG, " Stopping fg for service " + r); } setServiceForegroundInnerLocked(r, 0, null, 0, 0, - 0); + 0, /* systemRequestedTransition= */ true); } } @@ -1839,7 +1839,7 @@ public final class ActiveServices { ServiceRecord r = findServiceLocked(className, token, userId); if (r != null) { setServiceForegroundInnerLocked(r, id, notification, flags, foregroundServiceType, - callingUid); + callingUid, /* systemRequestedTransition= */ false); } } finally { mAm.mInjector.restoreCallingIdentity(origId); @@ -2155,7 +2155,7 @@ public final class ActiveServices { @GuardedBy("mAm") private void setServiceForegroundInnerLocked(final ServiceRecord r, int id, Notification notification, int flags, int foregroundServiceType, - int callingUidIfStart) { + int callingUidIfStart, boolean systemRequestedTransition) { if (id != 0) { if (notification == null) { throw new IllegalArgumentException("null notification"); @@ -2800,6 +2800,7 @@ public final class ActiveServices { // earlier. r.foregroundServiceType = 0; r.mFgsNotificationWasDeferred = false; + r.systemRequestedFgToBg = systemRequestedTransition; signalForegroundServiceObserversLocked(r); resetFgsRestrictionLocked(r); mAm.updateForegroundServiceUsageStats(r.name, r.userId, false); @@ -9339,14 +9340,22 @@ public final class ActiveServices { if (sr.foregroundServiceType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE && sr.foregroundId == notificationId) { - if (DEBUG_FOREGROUND_SERVICE) { - Slog.d(TAG, "Moving media service to foreground for package " - + packageName); + // check if service is explicitly requested by app to not be in foreground. + if (sr.systemRequestedFgToBg) { + Slog.d(TAG, + "System initiated service transition to foreground " + + "for package " + + packageName); + setServiceForegroundInnerLocked(sr, sr.foregroundId, + sr.foregroundNoti, /* flags */ 0, + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, + /* callingUidStart */ 0, /* systemRequestedTransition */ true); + } else { + Slog.d(TAG, + "Ignoring system initiated foreground service transition for " + + "package" + + packageName); } - setServiceForegroundInnerLocked(sr, sr.foregroundId, - sr.foregroundNoti, /* flags */ 0, - ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, - /* callingUidStart */ 0); } } } @@ -9379,13 +9388,14 @@ public final class ActiveServices { if (sr.foregroundServiceType == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK && sr.foregroundId == notificationId) { - if (DEBUG_FOREGROUND_SERVICE) { - Slog.d(TAG, "Forcing media foreground service to background for package " - + packageName); - } + Slog.d(TAG, + "System initiated transition of foreground service(type:media) to bg " + + "for package" + + packageName); setServiceForegroundInnerLocked(sr, /* id */ 0, /* notification */ null, /* flags */ 0, - /* foregroundServiceType */ 0, /* callingUidStart */ 0); + /* foregroundServiceType */ 0, /* callingUidStart */ 0, + /* systemRequestedTransition */ true); } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 6e3d7bd19b41..07a4d52f56ec 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -301,6 +301,7 @@ import android.content.pm.ProviderInfoList; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.SharedLibraryInfo; +import android.content.pm.SystemFeaturesCache; import android.content.pm.TestUtilityService; import android.content.pm.UserInfo; import android.content.pm.UserProperties; @@ -2559,9 +2560,20 @@ public class ActivityManagerService extends IActivityManager.Stub mTraceErrorLogger = new TraceErrorLogger(); mComponentAliasResolver = new ComponentAliasResolver(this); sCreatorTokenCacheCleaner = new Handler(mHandlerThread.getLooper()); + + ApplicationSharedMemory applicationSharedMemory = ApplicationSharedMemory.getInstance(); + if (android.content.pm.Flags.cacheSdkSystemFeatures()) { + // Install the cache into the process-wide singleton for in-proc queries, as well as + // shared memory. Apps will inflate the cache from shared memory in bindApplication. + SystemFeaturesCache systemFeaturesCache = + new SystemFeaturesCache(SystemConfig.getInstance().getAvailableFeatures()); + SystemFeaturesCache.setInstance(systemFeaturesCache); + applicationSharedMemory.writeSystemFeaturesCache( + systemFeaturesCache.getSdkFeatureVersions()); + } try { mApplicationSharedMemoryReadOnlyFd = - ApplicationSharedMemory.getInstance().getReadOnlyFileDescriptor(); + applicationSharedMemory.getReadOnlyFileDescriptor(); } catch (IOException e) { Slog.e(TAG, "Failed to get read only fd for shared memory", e); throw new RuntimeException(e); diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index 961022b7231b..517279bd7527 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -54,15 +54,21 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ProcessMap; import com.android.internal.os.Clock; import com.android.internal.os.MonotonicClock; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; import com.android.server.IoThread; import com.android.server.ServiceThread; import com.android.server.SystemServiceManager; import com.android.server.wm.WindowProcessController; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; @@ -1006,6 +1012,12 @@ public final class AppStartInfoTracker { throws IOException, WireTypeMismatchException, ClassNotFoundException { long token = proto.start(fieldId); String pkgName = ""; + + // Create objects for reuse. + ByteArrayInputStream byteArrayInputStream = null; + ObjectInputStream objectInputStream = null; + TypedXmlPullParser typedXmlPullParser = null; + for (int next = proto.nextField(); next != ProtoInputStream.NO_MORE_FIELDS; next = proto.nextField()) { @@ -1017,7 +1029,7 @@ public final class AppStartInfoTracker { AppStartInfoContainer container = new AppStartInfoContainer(mAppStartInfoHistoryListSize); int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS, - pkgName); + pkgName, byteArrayInputStream, objectInputStream, typedXmlPullParser); // If the isolated process flag is enabled and the uid is that of an isolated // process, then break early so that the container will not be added to mData. @@ -1052,6 +1064,12 @@ public final class AppStartInfoTracker { out = af.startWrite(); ProtoOutputStream proto = new ProtoOutputStream(out); proto.write(AppsStartInfoProto.LAST_UPDATE_TIMESTAMP, now); + + // Create objects for reuse. + ByteArrayOutputStream byteArrayOutputStream = null; + ObjectOutputStream objectOutputStream = null; + TypedXmlSerializer typedXmlSerializer = null; + synchronized (mLock) { succeeded = forEachPackageLocked( (packageName, records) -> { @@ -1060,8 +1078,9 @@ public final class AppStartInfoTracker { int uidArraySize = records.size(); for (int j = 0; j < uidArraySize; j++) { try { - records.valueAt(j) - .writeToProto(proto, AppsStartInfoProto.Package.USERS); + records.valueAt(j).writeToProto(proto, + AppsStartInfoProto.Package.USERS, byteArrayOutputStream, + objectOutputStream, typedXmlSerializer); } catch (IOException e) { Slog.w(TAG, "Unable to write app start info into persistent" + "storage: " + e); @@ -1414,19 +1433,23 @@ public final class AppStartInfoTracker { } @GuardedBy("mLock") - void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException { + void writeToProto(ProtoOutputStream proto, long fieldId, + ByteArrayOutputStream byteArrayOutputStream, ObjectOutputStream objectOutputStream, + TypedXmlSerializer typedXmlSerializer) throws IOException { long token = proto.start(fieldId); proto.write(AppsStartInfoProto.Package.User.UID, mUid); int size = mInfos.size(); for (int i = 0; i < size; i++) { - mInfos.get(i) - .writeToProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO); + mInfos.get(i).writeToProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO, + byteArrayOutputStream, objectOutputStream, typedXmlSerializer); } proto.write(AppsStartInfoProto.Package.User.MONITORING_ENABLED, mMonitoringModeEnabled); proto.end(token); } - int readFromProto(ProtoInputStream proto, long fieldId, String packageName) + int readFromProto(ProtoInputStream proto, long fieldId, String packageName, + ByteArrayInputStream byteArrayInputStream, ObjectInputStream objectInputStream, + TypedXmlPullParser typedXmlPullParser) throws IOException, WireTypeMismatchException, ClassNotFoundException { long token = proto.start(fieldId); for (int next = proto.nextField(); @@ -1440,7 +1463,8 @@ public final class AppStartInfoTracker { // Create record with monotonic time 0 in case the persisted record does not // have a create time. ApplicationStartInfo info = new ApplicationStartInfo(0); - info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO); + info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO, + byteArrayInputStream, objectInputStream, typedXmlPullParser); info.setPackageName(packageName); mInfos.add(info); break; diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index b677297dfef2..4bfee1d8398f 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -1464,7 +1464,10 @@ public class CachedAppOptimizer { void onProcessFrozen(ProcessRecord frozenProc) { if (useCompaction()) { synchronized (mProcLock) { - compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false); + // only full-compact if process is cached + if (frozenProc.mState.getSetAdj() >= mCompactThrottleMinOomAdj) { + compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false); + } } } frozenProc.onProcessFrozen(); diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS index cc6fabc8fd67..4b6d6bc955cc 100644 --- a/services/core/java/com/android/server/am/OWNERS +++ b/services/core/java/com/android/server/am/OWNERS @@ -68,7 +68,7 @@ per-file CarUserSwitchingDialog.java = file:platform/packages/services/Car:/OWNE per-file ActivityManager* = file:/ACTIVITY_SECURITY_OWNERS # Aconfig Flags -per-file flags.aconfig = yamasani@google.com, bills@google.com, nalini@google.com +per-file flags.aconfig = yamasani@google.com, nalini@google.com # Londoners michaelwr@google.com #{LAST_RESORT_SUGGESTION} @@ -77,4 +77,4 @@ narayan@google.com #{LAST_RESORT_SUGGESTION} # Default yamasani@google.com hackbod@google.com #{LAST_RESORT_SUGGESTION} -omakoto@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file +omakoto@google.com #{LAST_RESORT_SUGGESTION} diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 61c5501a7b5a..13d367a95942 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -446,6 +446,8 @@ public class OomAdjuster { private static final int CACHING_UI_SERVICE_CLIENT_ADJ_THRESHOLD = Flags.raiseBoundUiServiceThreshold() ? SERVICE_ADJ : PERCEPTIBLE_APP_ADJ; + static final long PERCEPTIBLE_TASK_TIMEOUT_MILLIS = 5 * 60 * 1000; + @VisibleForTesting public static class Injector { boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId, @@ -1847,7 +1849,7 @@ public class OomAdjuster { mHasVisibleActivities = false; } - void onOtherActivity() { + void onOtherActivity(long perceptibleTaskStoppedTimeMillis) { if (procState > PROCESS_STATE_CACHED_ACTIVITY) { procState = PROCESS_STATE_CACHED_ACTIVITY; mAdjType = "cch-act"; @@ -1856,6 +1858,28 @@ public class OomAdjuster { "Raise procstate to cached activity: " + app); } } + if (Flags.perceptibleTasks() && adj > PERCEPTIBLE_MEDIUM_APP_ADJ) { + if (perceptibleTaskStoppedTimeMillis >= 0) { + final long now = mInjector.getUptimeMillis(); + if (now - perceptibleTaskStoppedTimeMillis < PERCEPTIBLE_TASK_TIMEOUT_MILLIS) { + adj = PERCEPTIBLE_MEDIUM_APP_ADJ; + mAdjType = "perceptible-act"; + if (procState > PROCESS_STATE_IMPORTANT_BACKGROUND) { + procState = PROCESS_STATE_IMPORTANT_BACKGROUND; + } + + maybeSetProcessFollowUpUpdateLocked(app, + perceptibleTaskStoppedTimeMillis + PERCEPTIBLE_TASK_TIMEOUT_MILLIS, + now); + } else if (adj > PREVIOUS_APP_ADJ) { + adj = PREVIOUS_APP_ADJ; + mAdjType = "stale-perceptible-act"; + if (procState > PROCESS_STATE_LAST_ACTIVITY) { + procState = PROCESS_STATE_LAST_ACTIVITY; + } + } + } + } mHasVisibleActivities = false; } } diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index b0f808b39053..25175e6bee5f 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -1120,7 +1120,8 @@ final class ProcessStateRecord { } else if ((flags & ACTIVITY_STATE_FLAG_IS_STOPPING) != 0) { callback.onStoppingActivity((flags & ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) != 0); } else { - callback.onOtherActivity(); + final long ts = mApp.getWindowProcessController().getPerceptibleTaskStoppedTimeMillis(); + callback.onOtherActivity(ts); } mCachedAdj = callback.adj; diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index ca34a13c55b1..f443e4455734 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -158,6 +158,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN boolean fgWaiting; // is a timeout for going foreground already scheduled? boolean isNotAppComponentUsage; // is service binding not considered component/package usage? boolean isForeground; // is service currently in foreground mode? + boolean systemRequestedFgToBg; // system requested service to transition to background. boolean inSharedIsolatedProcess; // is the service in a shared isolated process int foregroundId; // Notification ID of last foreground req. Notification foregroundNoti; // Notification record of foreground state. diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 27c384a22fb6..c8fedf3d1765 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -293,6 +293,13 @@ flag { } flag { + name: "perceptible_tasks" + namespace: "system_performance" + description: "Boost the oom_score_adj of activities in perceptible tasks" + bug: "370890207" +} + +flag { name: "expedite_activity_launch_on_cold_start" namespace: "system_performance" description: "Notify ActivityTaskManager of cold starts early to fix app launch behavior." diff --git a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java index 30c2a82296ca..604cb30294a9 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java @@ -418,7 +418,9 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { evictedEvents.addAll(mCache); mCache.clear(); } - mSqliteWriteHandler.obtainMessage(WRITE_CACHE_EVICTED_OP_EVENTS, evictedEvents); + Message msg = mSqliteWriteHandler.obtainMessage( + WRITE_CACHE_EVICTED_OP_EVENTS, evictedEvents); + mSqliteWriteHandler.sendMessage(msg); } } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 6d6e1fb6bfb3..ef80d59993e9 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -18,6 +18,7 @@ package com.android.server.audio; import static android.media.audio.Flags.scoManagedByAudio; import static com.android.media.audio.Flags.equalScoLeaVcIndexRange; +import static com.android.media.audio.Flags.optimizeBtDeviceSwitch; import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_BLE_HEADSET; import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER; import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_SCO; @@ -290,8 +291,8 @@ public class AudioDeviceBroker { } @GuardedBy("mDeviceStateLock") - /*package*/ void onSetBtScoActiveDevice(BluetoothDevice btDevice) { - mBtHelper.onSetBtScoActiveDevice(btDevice); + /*package*/ void onSetBtScoActiveDevice(BluetoothDevice btDevice, boolean deviceSwitch) { + mBtHelper.onSetBtScoActiveDevice(btDevice, deviceSwitch); } /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) { @@ -941,6 +942,7 @@ public class AudioDeviceBroker { final @NonNull String mEventSource; final int mAudioSystemDevice; final int mMusicDevice; + final boolean mIsDeviceSwitch; BtDeviceInfo(@NonNull BtDeviceChangedData d, @NonNull BluetoothDevice device, int state, int audioDevice, @AudioSystem.AudioFormatNativeEnumForBtCodec int codec) { @@ -953,6 +955,8 @@ public class AudioDeviceBroker { mEventSource = d.mEventSource; mAudioSystemDevice = audioDevice; mMusicDevice = AudioSystem.DEVICE_NONE; + mIsDeviceSwitch = optimizeBtDeviceSwitch() + && d.mNewDevice != null && d.mPreviousDevice != null; } // constructor used by AudioDeviceBroker to search similar message @@ -966,6 +970,7 @@ public class AudioDeviceBroker { mSupprNoisy = false; mVolume = -1; mIsLeOutput = false; + mIsDeviceSwitch = false; } // constructor used by AudioDeviceInventory when config change failed @@ -980,6 +985,7 @@ public class AudioDeviceBroker { mSupprNoisy = false; mVolume = -1; mIsLeOutput = false; + mIsDeviceSwitch = false; } BtDeviceInfo(@NonNull BtDeviceInfo src, int state) { @@ -992,6 +998,7 @@ public class AudioDeviceBroker { mEventSource = src.mEventSource; mAudioSystemDevice = src.mAudioSystemDevice; mMusicDevice = src.mMusicDevice; + mIsDeviceSwitch = false; } // redefine equality op so we can match messages intended for this device @@ -1026,7 +1033,8 @@ public class AudioDeviceBroker { + " isLeOutput=" + mIsLeOutput + " eventSource=" + mEventSource + " audioSystemDevice=" + mAudioSystemDevice - + " musicDevice=" + mMusicDevice; + + " musicDevice=" + mMusicDevice + + " isDeviceSwitch=" + mIsDeviceSwitch; } } @@ -1196,6 +1204,8 @@ public class AudioDeviceBroker { AudioSystem.setParameters("A2dpSuspended=true"); AudioSystem.setParameters("LeAudioSuspended=true"); AudioSystem.setParameters("BT_SCO=on"); + mBluetoothA2dpSuspendedApplied = true; + mBluetoothLeSuspendedApplied = true; } else { AudioSystem.setParameters("BT_SCO=off"); if (mBluetoothA2dpSuspendedApplied) { @@ -1680,10 +1690,11 @@ public class AudioDeviceBroker { } /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes, - boolean connect, @Nullable BluetoothDevice btDevice) { + boolean connect, @Nullable BluetoothDevice btDevice, + boolean deviceSwitch) { synchronized (mDeviceStateLock) { return mDeviceInventory.handleDeviceConnection( - attributes, connect, false /*for test*/, btDevice); + attributes, connect, false /*for test*/, btDevice, deviceSwitch); } } @@ -1776,6 +1787,18 @@ public class AudioDeviceBroker { pw.println("\n" + prefix + "mScoManagedByAudio: " + mScoManagedByAudio); + pw.println("\n" + prefix + "Bluetooth SCO on" + + ", requested: " + mBluetoothScoOn + + ", applied: " + mBluetoothScoOnApplied); + pw.println("\n" + prefix + "Bluetooth A2DP suspended" + + ", requested ext: " + mBluetoothA2dpSuspendedExt + + ", requested int: " + mBluetoothA2dpSuspendedInt + + ", applied " + mBluetoothA2dpSuspendedApplied); + pw.println("\n" + prefix + "Bluetooth LE Audio suspended" + + ", requested ext: " + mBluetoothLeSuspendedExt + + ", requested int: " + mBluetoothLeSuspendedInt + + ", applied " + mBluetoothLeSuspendedApplied); + mBtHelper.dump(pw, prefix); } @@ -1930,10 +1953,12 @@ public class AudioDeviceBroker { || btInfo.mIsLeOutput) ? mAudioService.getBluetoothContextualVolumeStream() : AudioSystem.STREAM_DEFAULT); - if (btInfo.mProfile == BluetoothProfile.LE_AUDIO + if ((btInfo.mProfile == BluetoothProfile.LE_AUDIO || btInfo.mProfile == BluetoothProfile.HEARING_AID || (mScoManagedByAudio - && btInfo.mProfile == BluetoothProfile.HEADSET)) { + && btInfo.mProfile == BluetoothProfile.HEADSET)) + && (btInfo.mState == BluetoothProfile.STATE_CONNECTED + || !btInfo.mIsDeviceSwitch)) { onUpdateCommunicationRouteClient( bluetoothScoRequestOwnerAttributionSource(), "setBluetoothActiveDevice"); diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index ef10793fd955..ae91934e7498 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -799,7 +799,7 @@ public class AudioDeviceInventory { di.mDeviceAddress, di.mDeviceName), AudioSystem.DEVICE_STATE_AVAILABLE, - di.mDeviceCodecFormat); + di.mDeviceCodecFormat, false /*deviceSwitch*/); if (asDeviceConnectionFailure() && res != AudioSystem.AUDIO_STATUS_OK) { failedReconnectionDeviceList.add(di); } @@ -811,7 +811,7 @@ public class AudioDeviceInventory { EventLogger.Event.ALOGE, TAG); mConnectedDevices.remove(di.getKey(), di); if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) { - mDeviceBroker.onSetBtScoActiveDevice(null); + mDeviceBroker.onSetBtScoActiveDevice(null, false /*deviceSwitch*/); } } } @@ -851,7 +851,8 @@ public class AudioDeviceInventory { Log.d(TAG, "onSetBtActiveDevice" + " btDevice=" + btInfo.mDevice + " profile=" + BluetoothProfile.getProfileName(btInfo.mProfile) - + " state=" + BluetoothProfile.getConnectionStateName(btInfo.mState)); + + " state=" + BluetoothProfile.getConnectionStateName(btInfo.mState) + + " isDeviceSwitch=" + btInfo.mIsDeviceSwitch); } String address = btInfo.mDevice.getAddress(); if (!BluetoothAdapter.checkBluetoothAddress(address)) { @@ -897,7 +898,8 @@ public class AudioDeviceInventory { break; case BluetoothProfile.A2DP: if (switchToUnavailable) { - makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat); + makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat, + btInfo.mIsDeviceSwitch); } else if (switchToAvailable) { // device is not already connected if (btInfo.mVolume != -1) { @@ -911,7 +913,7 @@ public class AudioDeviceInventory { break; case BluetoothProfile.HEARING_AID: if (switchToUnavailable) { - makeHearingAidDeviceUnavailable(address); + makeHearingAidDeviceUnavailable(address, btInfo.mIsDeviceSwitch); } else if (switchToAvailable) { makeHearingAidDeviceAvailable(address, BtHelper.getName(btInfo.mDevice), streamType, "onSetBtActiveDevice"); @@ -921,7 +923,8 @@ public class AudioDeviceInventory { case BluetoothProfile.LE_AUDIO_BROADCAST: if (switchToUnavailable) { makeLeAudioDeviceUnavailableNow(address, - btInfo.mAudioSystemDevice, di.mDeviceCodecFormat); + btInfo.mAudioSystemDevice, di.mDeviceCodecFormat, + btInfo.mIsDeviceSwitch); } else if (switchToAvailable) { makeLeAudioDeviceAvailable( btInfo, streamType, codec, "onSetBtActiveDevice"); @@ -930,9 +933,10 @@ public class AudioDeviceInventory { case BluetoothProfile.HEADSET: if (mDeviceBroker.isScoManagedByAudio()) { if (switchToUnavailable) { - mDeviceBroker.onSetBtScoActiveDevice(null); + mDeviceBroker.onSetBtScoActiveDevice(null, btInfo.mIsDeviceSwitch); } else if (switchToAvailable) { - mDeviceBroker.onSetBtScoActiveDevice(btInfo.mDevice); + mDeviceBroker.onSetBtScoActiveDevice( + btInfo.mDevice, false /*deviceSwitch*/); } } break; @@ -1053,19 +1057,19 @@ public class AudioDeviceInventory { /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { synchronized (mDevicesLock) { - makeA2dpDeviceUnavailableNow(address, a2dpCodec); + makeA2dpDeviceUnavailableNow(address, a2dpCodec, false /*deviceSwitch*/); } } /*package*/ void onMakeLeAudioDeviceUnavailableNow(String address, int device, int codec) { synchronized (mDevicesLock) { - makeLeAudioDeviceUnavailableNow(address, device, codec); + makeLeAudioDeviceUnavailableNow(address, device, codec, false /*deviceSwitch*/); } } /*package*/ void onMakeHearingAidDeviceUnavailableNow(String address) { synchronized (mDevicesLock) { - makeHearingAidDeviceUnavailable(address); + makeHearingAidDeviceUnavailable(address, false /*deviceSwitch*/); } } @@ -1180,7 +1184,8 @@ public class AudioDeviceInventory { } if (!handleDeviceConnection(wdcs.mAttributes, - wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest, null)) { + wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest, + null, false /*deviceSwitch*/)) { // change of connection state failed, bailout mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed") .record(); @@ -1788,14 +1793,15 @@ public class AudioDeviceInventory { */ /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes, boolean connect, boolean isForTesting, - @Nullable BluetoothDevice btDevice) { + @Nullable BluetoothDevice btDevice, + boolean deviceSwitch) { int device = attributes.getInternalType(); String address = attributes.getAddress(); String deviceName = attributes.getName(); if (AudioService.DEBUG_DEVICES) { Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + Integer.toHexString(device) + " address:" + address - + " name:" + deviceName + ")"); + + " name:" + deviceName + ", deviceSwitch: " + deviceSwitch + ")"); } MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "handleDeviceConnection") .set(MediaMetrics.Property.ADDRESS, address) @@ -1829,7 +1835,8 @@ public class AudioDeviceInventory { res = AudioSystem.AUDIO_STATUS_OK; } else { res = mAudioSystem.setDeviceConnectionState(attributes, - AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); + AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT, + false /*deviceSwitch*/); } if (res != AudioSystem.AUDIO_STATUS_OK) { final String reason = "not connecting device 0x" + Integer.toHexString(device) @@ -1856,7 +1863,8 @@ public class AudioDeviceInventory { status = true; } else if (!connect && isConnected) { mAudioSystem.setDeviceConnectionState(attributes, - AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); + AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT, + deviceSwitch); // always remove even if disconnection failed mConnectedDevices.remove(deviceKey); mDeviceBroker.postCheckCommunicationDeviceRemoval(attributes); @@ -2030,7 +2038,7 @@ public class AudioDeviceInventory { } } if (disconnect) { - mDeviceBroker.onSetBtScoActiveDevice(null); + mDeviceBroker.onSetBtScoActiveDevice(null, false /*deviceSwitch*/); } } @@ -2068,7 +2076,8 @@ public class AudioDeviceInventory { || info.mProfile == BluetoothProfile.LE_AUDIO_BROADCAST) && info.mIsLeOutput) || info.mProfile == BluetoothProfile.HEARING_AID - || info.mProfile == BluetoothProfile.A2DP)) { + || info.mProfile == BluetoothProfile.A2DP) + && !info.mIsDeviceSwitch) { @AudioService.ConnectionState int asState = (info.mState == BluetoothProfile.STATE_CONNECTED) ? AudioService.CONNECTION_STATE_CONNECTED @@ -2124,7 +2133,7 @@ public class AudioDeviceInventory { AudioDeviceAttributes ada = new AudioDeviceAttributes( AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name); final int res = mAudioSystem.setDeviceConnectionState(ada, - AudioSystem.DEVICE_STATE_AVAILABLE, codec); + AudioSystem.DEVICE_STATE_AVAILABLE, codec, false); // TODO: log in MediaMetrics once distinction between connection failure and // double connection is made. @@ -2362,7 +2371,7 @@ public class AudioDeviceInventory { } @GuardedBy("mDevicesLock") - private void makeA2dpDeviceUnavailableNow(String address, int codec) { + private void makeA2dpDeviceUnavailableNow(String address, int codec, boolean deviceSwitch) { MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "a2dp." + address) .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(codec)) .set(MediaMetrics.Property.EVENT, "makeA2dpDeviceUnavailableNow"); @@ -2393,7 +2402,7 @@ public class AudioDeviceInventory { AudioDeviceAttributes ada = new AudioDeviceAttributes( AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); final int res = mAudioSystem.setDeviceConnectionState(ada, - AudioSystem.DEVICE_STATE_UNAVAILABLE, codec); + AudioSystem.DEVICE_STATE_UNAVAILABLE, codec, deviceSwitch); if (res != AudioSystem.AUDIO_STATUS_OK) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( @@ -2404,7 +2413,8 @@ public class AudioDeviceInventory { } else { AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( "A2DP device addr=" + Utils.anonymizeBluetoothAddress(address) - + " made unavailable")).printSlog(EventLogger.Event.ALOGI, TAG)); + + " made unavailable, deviceSwitch" + deviceSwitch)) + .printSlog(EventLogger.Event.ALOGI, TAG)); } mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); @@ -2440,7 +2450,7 @@ public class AudioDeviceInventory { final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), AudioSystem.DEVICE_STATE_AVAILABLE, - AudioSystem.AUDIO_FORMAT_DEFAULT); + AudioSystem.AUDIO_FORMAT_DEFAULT, false); if (res != AudioSystem.AUDIO_STATUS_OK) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "APM failed to make available A2DP source device addr=" @@ -2465,7 +2475,7 @@ public class AudioDeviceInventory { AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_UNAVAILABLE, - AudioSystem.AUDIO_FORMAT_DEFAULT); + AudioSystem.AUDIO_FORMAT_DEFAULT, false); // always remove regardless of the result mConnectedDevices.remove( DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); @@ -2485,7 +2495,7 @@ public class AudioDeviceInventory { DEVICE_OUT_HEARING_AID, address, name); final int res = mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE, - AudioSystem.AUDIO_FORMAT_DEFAULT); + AudioSystem.AUDIO_FORMAT_DEFAULT, false); if (asDeviceConnectionFailure() && res != AudioSystem.AUDIO_STATUS_OK) { AudioService.sDeviceLogger.enqueueAndSlog( "APM failed to make available HearingAid addr=" + address @@ -2515,12 +2525,12 @@ public class AudioDeviceInventory { } @GuardedBy("mDevicesLock") - private void makeHearingAidDeviceUnavailable(String address) { + private void makeHearingAidDeviceUnavailable(String address, boolean deviceSwitch) { AudioDeviceAttributes ada = new AudioDeviceAttributes( DEVICE_OUT_HEARING_AID, address); mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_UNAVAILABLE, - AudioSystem.AUDIO_FORMAT_DEFAULT); + AudioSystem.AUDIO_FORMAT_DEFAULT, deviceSwitch); // always remove regardless of return code mConnectedDevices.remove( DeviceInfo.makeDeviceListKey(DEVICE_OUT_HEARING_AID, address)); @@ -2622,7 +2632,7 @@ public class AudioDeviceInventory { AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name); final int res = mAudioSystem.setDeviceConnectionState(ada, - AudioSystem.DEVICE_STATE_AVAILABLE, codec); + AudioSystem.DEVICE_STATE_AVAILABLE, codec, false /*deviceSwitch*/); if (res != AudioSystem.AUDIO_STATUS_OK) { AudioService.sDeviceLogger.enqueueAndSlog( "APM failed to make available LE Audio device addr=" + address @@ -2669,13 +2679,13 @@ public class AudioDeviceInventory { @GuardedBy("mDevicesLock") private void makeLeAudioDeviceUnavailableNow(String address, int device, - @AudioSystem.AudioFormatNativeEnumForBtCodec int codec) { + @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, boolean deviceSwitch) { AudioDeviceAttributes ada = null; if (device != AudioSystem.DEVICE_NONE) { ada = new AudioDeviceAttributes(device, address); final int res = mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_UNAVAILABLE, - codec); + codec, deviceSwitch); if (res != AudioSystem.AUDIO_STATUS_OK) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( @@ -2685,7 +2695,8 @@ public class AudioDeviceInventory { } else { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "LE Audio device addr=" + Utils.anonymizeBluetoothAddress(address) - + " made unavailable").printSlog(EventLogger.Event.ALOGI, TAG)); + + " made unavailable, deviceSwitch" + deviceSwitch) + .printSlog(EventLogger.Event.ALOGI, TAG)); } mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 86871ea45d13..d800503c658e 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -63,6 +63,7 @@ import static com.android.media.audio.Flags.audioserverPermissions; import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume; import static com.android.media.audio.Flags.deferWearPermissionUpdates; import static com.android.media.audio.Flags.equalScoLeaVcIndexRange; +import static com.android.media.audio.Flags.optimizeBtDeviceSwitch; import static com.android.media.audio.Flags.replaceStreamBtSco; import static com.android.media.audio.Flags.ringMyCar; import static com.android.media.audio.Flags.ringerModeAffectsAlarm; @@ -585,6 +586,9 @@ public class AudioService extends IAudioService.Stub // protects mRingerMode private final Object mSettingsLock = new Object(); + // protects VolumeStreamState / VolumeGroupState operations + private final Object mVolumeStateLock = new Object(); + /** Maximum volume index values for audio streams */ protected static int[] MAX_STREAM_VOLUME = new int[] { 5, // STREAM_VOICE_CALL @@ -1611,7 +1615,7 @@ public class AudioService extends IAudioService.Stub private void initVolumeStreamStates() { int numStreamTypes = AudioSystem.getNumStreamTypes(); - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { final VolumeStreamState streamState = getVssForStream(streamType); if (streamState == null) { @@ -2419,7 +2423,7 @@ public class AudioService extends IAudioService.Stub private void checkAllAliasStreamVolumes() { synchronized (mSettingsLock) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = 0; streamType < numStreamTypes; streamType++) { int streamAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1); @@ -2501,7 +2505,7 @@ public class AudioService extends IAudioService.Stub private void onUpdateVolumeStatesForAudioDevice(int device, String caller) { final int numStreamTypes = AudioSystem.getNumStreamTypes(); synchronized (mSettingsLock) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { for (int streamType = 0; streamType < numStreamTypes; streamType++) { updateVolumeStates(device, streamType, caller); } @@ -2770,7 +2774,7 @@ public class AudioService extends IAudioService.Stub updateDefaultVolumes(); synchronized (mSettingsLock) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { getVssForStreamOrDefault(AudioSystem.STREAM_DTMF) .setAllIndexes(getVssForStreamOrDefault(dtmfStreamAlias), caller); getVssForStreamOrDefault(AudioSystem.STREAM_ACCESSIBILITY).setSettingName( @@ -3232,8 +3236,10 @@ public class AudioService extends IAudioService.Stub // Each stream will read its own persisted settings // Broadcast the sticky intents - broadcastRingerMode(AudioManager.RINGER_MODE_CHANGED_ACTION, mRingerModeExternal); - broadcastRingerMode(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, mRingerMode); + synchronized (mSettingsLock) { + broadcastRingerMode(AudioManager.RINGER_MODE_CHANGED_ACTION, mRingerModeExternal); + broadcastRingerMode(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, mRingerMode); + } // Broadcast vibrate settings broadcastVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER); @@ -4235,7 +4241,7 @@ public class AudioService extends IAudioService.Stub private void muteAliasStreams(int streamAlias, boolean state) { // Locking mSettingsLock to avoid inversion when calling doMute -> updateVolumeGroupIndex synchronized (mSettingsLock) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { List<Integer> streamsToMute = new ArrayList<>(); for (int streamIdx = 0; streamIdx < mStreamStates.size(); streamIdx++) { final VolumeStreamState vss = mStreamStates.valueAt(streamIdx); @@ -4279,7 +4285,7 @@ public class AudioService extends IAudioService.Stub // Locking mSettingsLock to avoid inversion when calling vss.mute -> vss.doMute -> // vss.updateVolumeGroupIndex synchronized (mSettingsLock) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { final VolumeStreamState streamState = getVssForStreamOrDefault(streamAlias); // if unmuting causes a change, it was muted wasMuted = streamState.mute(false, "onUnmuteStreamOnSingleVolDevice"); @@ -4455,7 +4461,7 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#getVolumeGroupVolumeIndex(int) */ public int getVolumeGroupVolumeIndex(int groupId) { super.getVolumeGroupVolumeIndex_enforcePermission(); - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { if (sVolumeGroupStates.indexOfKey(groupId) < 0) { Log.e(TAG, "No volume group for id " + groupId); return 0; @@ -4472,7 +4478,7 @@ public class AudioService extends IAudioService.Stub MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING }) public int getVolumeGroupMaxVolumeIndex(int groupId) { super.getVolumeGroupMaxVolumeIndex_enforcePermission(); - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { if (sVolumeGroupStates.indexOfKey(groupId) < 0) { Log.e(TAG, "No volume group for id " + groupId); return 0; @@ -4487,7 +4493,7 @@ public class AudioService extends IAudioService.Stub MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING }) public int getVolumeGroupMinVolumeIndex(int groupId) { super.getVolumeGroupMinVolumeIndex_enforcePermission(); - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { if (sVolumeGroupStates.indexOfKey(groupId) < 0) { Log.e(TAG, "No volume group for id " + groupId); return 0; @@ -4632,7 +4638,7 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(QUERY_AUDIO_STATE) public int getLastAudibleVolumeForVolumeGroup(int groupId) { super.getLastAudibleVolumeForVolumeGroup_enforcePermission(); - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { if (sVolumeGroupStates.indexOfKey(groupId) < 0) { Log.e(TAG, ": no volume group found for id " + groupId); return 0; @@ -4644,7 +4650,7 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#isVolumeGroupMuted(int) */ public boolean isVolumeGroupMuted(int groupId) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { if (sVolumeGroupStates.indexOfKey(groupId) < 0) { Log.e(TAG, ": no volume group found for id " + groupId); return false; @@ -4990,6 +4996,8 @@ public class AudioService extends IAudioService.Stub + cacheGetStreamMinMaxVolume()); pw.println("\tandroid.media.audio.Flags.cacheGetStreamVolume:" + cacheGetStreamVolume()); + pw.println("\tcom.android.media.audio.optimizeBtDeviceSwitch:" + + optimizeBtDeviceSwitch()); } private void dumpAudioMode(PrintWriter pw) { @@ -5470,7 +5478,7 @@ public class AudioService extends IAudioService.Stub } streamType = replaceBtScoStreamWithVoiceCall(streamType, "isStreamMute"); - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { ensureValidStreamType(streamType); return getVssForStreamOrDefault(streamType).mIsMuted; } @@ -5651,7 +5659,7 @@ public class AudioService extends IAudioService.Stub } private int getStreamVolume(int streamType, int device) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { final VolumeStreamState vss = getVssForStreamOrDefault(streamType); int index = vss.getIndex(device); @@ -5695,7 +5703,7 @@ public class AudioService extends IAudioService.Stub vib.setMinVolumeIndex((vss.mIndexMin + 5) / 10); vib.setMaxVolumeIndex((vss.mIndexMax + 5) / 10); - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { final int index; if (isFixedVolumeDevice(ada.getInternalType())) { index = (vss.mIndexMax + 5) / 10; @@ -6263,7 +6271,7 @@ public class AudioService extends IAudioService.Stub // ring and notifications volume should never be 0 when not silenced if (sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_RING || sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_NOTIFICATION) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { for (int i = 0; i < vss.mIndexMap.size(); i++) { int device = vss.mIndexMap.keyAt(i); int value = vss.mIndexMap.valueAt(i); @@ -7023,7 +7031,7 @@ public class AudioService extends IAudioService.Stub } streamState.readSettings(); - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { // unmute stream that was muted but is not affect by mute anymore if (streamState.mIsMuted && ((!isStreamAffectedByMute(streamType) && !isStreamMutedByRingerOrZenMode(streamType)) || mUseFixedVolume)) { @@ -8056,14 +8064,14 @@ public class AudioService extends IAudioService.Stub public Set<Integer> getDeviceSetForStream(int stream) { stream = replaceBtScoStreamWithVoiceCall(stream, "getDeviceSetForStream"); ensureValidStreamType(stream); - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { return getVssForStreamOrDefault(stream).observeDevicesForStream_syncVSS(true); } } private void onObserveDevicesForAllStreams(int skipStream) { synchronized (mSettingsLock) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { for (int stream = 0; stream < mStreamStates.size(); stream++) { final VolumeStreamState vss = mStreamStates.valueAt(stream); if (vss != null && vss.getStreamType() != skipStream) { @@ -8645,7 +8653,7 @@ public class AudioService extends IAudioService.Stub private void readVolumeGroupsSettings(boolean userSwitch) { synchronized (mSettingsLock) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { if (DEBUG_VOL) { Log.d(TAG, "readVolumeGroupsSettings userSwitch=" + userSwitch); } @@ -8703,7 +8711,7 @@ public class AudioService extends IAudioService.Stub // NOTE: Locking order for synchronized objects related to volume management: // 1 mSettingsLock - // 2 VolumeStreamState.class + // 2 mVolumeStateLock private class VolumeGroupState { private final AudioVolumeGroup mAudioVolumeGroup; private final SparseIntArray mIndexMap = new SparseIntArray(8); @@ -8797,7 +8805,7 @@ public class AudioService extends IAudioService.Stub * Mute/unmute the volume group * @param muted the new mute state */ - @GuardedBy("AudioService.VolumeStreamState.class") + @GuardedBy("AudioService.this.mVolumeStateLock") public boolean mute(boolean muted) { if (!isMutable()) { // Non mutable volume group @@ -8821,7 +8829,7 @@ public class AudioService extends IAudioService.Stub public void adjustVolume(int direction, int flags) { synchronized (mSettingsLock) { - synchronized (AudioService.VolumeStreamState.class) { + synchronized (mVolumeStateLock) { int device = getDeviceForVolume(); int previousIndex = getIndex(device); if (isMuteAdjust(direction) && !isMutable()) { @@ -8875,14 +8883,14 @@ public class AudioService extends IAudioService.Stub } public int getVolumeIndex() { - synchronized (AudioService.VolumeStreamState.class) { + synchronized (mVolumeStateLock) { return getIndex(getDeviceForVolume()); } } public void setVolumeIndex(int index, int flags) { synchronized (mSettingsLock) { - synchronized (AudioService.VolumeStreamState.class) { + synchronized (mVolumeStateLock) { if (mUseFixedVolume) { return; } @@ -8891,7 +8899,7 @@ public class AudioService extends IAudioService.Stub } } - @GuardedBy("AudioService.VolumeStreamState.class") + @GuardedBy("AudioService.this.mVolumeStateLock") private void setVolumeIndex(int index, int device, int flags) { // Update cache & persist (muted by volume 0 shall be persisted) updateVolumeIndex(index, device); @@ -8904,7 +8912,7 @@ public class AudioService extends IAudioService.Stub } } - @GuardedBy("AudioService.VolumeStreamState.class") + @GuardedBy("AudioService.this.mVolumeStateLock") public void updateVolumeIndex(int index, int device) { // Filter persistency if already exist and the index has not changed if (mIndexMap.indexOfKey(device) < 0 || mIndexMap.get(device) != index) { @@ -8922,7 +8930,7 @@ public class AudioService extends IAudioService.Stub } } - @GuardedBy("AudioService.VolumeStreamState.class") + @GuardedBy("AudioService.this.mVolumeStateLock") private void setVolumeIndexInt(int index, int device, int flags) { // Reflect mute state of corresponding stream by forcing index to 0 if muted // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted. @@ -8955,14 +8963,14 @@ public class AudioService extends IAudioService.Stub mAudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, muted, device); } - @GuardedBy("AudioService.VolumeStreamState.class") + @GuardedBy("AudioService.this.mVolumeStateLock") private int getIndex(int device) { int index = mIndexMap.get(device, -1); // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT); } - @GuardedBy("AudioService.VolumeStreamState.class") + @GuardedBy("AudioService.this.mVolumeStateLock") private boolean hasIndexForDevice(int device) { return (mIndexMap.get(device, -1) != -1); } @@ -8989,7 +8997,7 @@ public class AudioService extends IAudioService.Stub public void applyAllVolumes(boolean userSwitch) { String caller = "from vgs"; - synchronized (AudioService.VolumeStreamState.class) { + synchronized (mVolumeStateLock) { // apply device specific volumes first for (int i = 0; i < mIndexMap.size(); i++) { int device = mIndexMap.keyAt(i); @@ -9092,25 +9100,27 @@ public class AudioService extends IAudioService.Stub if (mUseFixedVolume || mHasValidStreamType) { return; } - if (DEBUG_VOL) { - Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group " - + mAudioVolumeGroup.name() - + ", device " + AudioSystem.getOutputDeviceName(device) - + " and User=" + getCurrentUserId() - + " mSettingName: " + mSettingName); - } + synchronized (mVolumeStateLock) { + if (DEBUG_VOL) { + Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + + " for group " + mAudioVolumeGroup.name() + + ", device " + AudioSystem.getOutputDeviceName(device) + + " and User=" + getCurrentUserId() + + " mSettingName: " + mSettingName); + } - boolean success = mSettings.putSystemIntForUser(mContentResolver, - getSettingNameForDevice(device), - getIndex(device), - getVolumePersistenceUserId()); - if (!success) { - Log.e(TAG, "persistVolumeGroup failed for group " + mAudioVolumeGroup.name()); + boolean success = mSettings.putSystemIntForUser(mContentResolver, + getSettingNameForDevice(device), + getIndex(device), + getVolumePersistenceUserId()); + if (!success) { + Log.e(TAG, "persistVolumeGroup failed for group " + mAudioVolumeGroup.name()); + } } } public void readSettings() { - synchronized (AudioService.VolumeStreamState.class) { + synchronized (mVolumeStateLock) { // force maximum volume on all streams if fixed volume property is set if (mUseFixedVolume) { mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax); @@ -9144,7 +9154,7 @@ public class AudioService extends IAudioService.Stub } } - @GuardedBy("AudioService.VolumeStreamState.class") + @GuardedBy("AudioService.this.mVolumeStateLock") private int getValidIndex(int index) { if (index < mIndexMin) { return mIndexMin; @@ -9218,7 +9228,7 @@ public class AudioService extends IAudioService.Stub // 1 mScoclient OR mSafeMediaVolumeState // 2 mSetModeLock // 3 mSettingsLock - // 4 VolumeStreamState.class + // 4 mVolumeStateLock /*package*/ class VolumeStreamState { private final int mStreamType; private VolumeGroupState mVolumeGroupState = null; @@ -9427,7 +9437,7 @@ public class AudioService extends IAudioService.Stub * * This is a reference to the local list, do not modify. */ - @GuardedBy("VolumeStreamState.class") + @GuardedBy("mVolumeStateLock") @NonNull public Set<Integer> observeDevicesForStream_syncVSS( boolean checkOthers) { @@ -9495,7 +9505,7 @@ public class AudioService extends IAudioService.Stub public void readSettings() { synchronized (mSettingsLock) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { // force maximum volume on all streams if fixed volume property is set if (mUseFixedVolume) { mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax); @@ -9515,7 +9525,7 @@ public class AudioService extends IAudioService.Stub } } } - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { for (int device : AudioSystem.DEVICE_OUT_ALL_SET) { // retrieve current volume for device @@ -9548,7 +9558,7 @@ public class AudioService extends IAudioService.Stub * will send the non-zero index together with muted state. Otherwise, index 0 will be sent * to native for signalising a muted stream. **/ - @GuardedBy("VolumeStreamState.class") + @GuardedBy("mVolumeStateLock") private void setStreamVolumeIndex(int index, int device) { // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted. // This allows RX path muting by the audio HAL only when explicitly muted but not when @@ -9570,8 +9580,8 @@ public class AudioService extends IAudioService.Stub mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, muted, device); } - // must be called while synchronized VolumeStreamState.class - @GuardedBy("VolumeStreamState.class") + // must be called while synchronized mVolumeStateLock + @GuardedBy("mVolumeStateLock") /*package*/ void applyDeviceVolume_syncVSS(int device) { int index; if (isFullyMuted() && !ringMyCar()) { @@ -9597,7 +9607,7 @@ public class AudioService extends IAudioService.Stub } public void applyAllVolumes() { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { // apply device specific volumes first int index; boolean isAbsoluteVolume = false; @@ -9659,7 +9669,7 @@ public class AudioService extends IAudioService.Stub final boolean isCurrentDevice; final StringBuilder aliasStreamIndexes = new StringBuilder(); synchronized (mSettingsLock) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { oldIndex = getIndex(device); index = getValidIndex(index, hasModifyAudioSettings); // for STREAM_SYSTEM_ENFORCED, do not sync aliased streams on the enforced index @@ -9708,76 +9718,82 @@ public class AudioService extends IAudioService.Stub } } } - } - } - if (changed) { - // If associated to volume group, update group cache - updateVolumeGroupIndex(device, /* forceMuteState= */ false); - - oldIndex = (oldIndex + 5) / 10; - index = (index + 5) / 10; - // log base stream changes to the event log - if (sStreamVolumeAlias.get(mStreamType, /*valueIfKeyNotFound=*/-1) == mStreamType) { - if (caller == null) { - Log.w(TAG, "No caller for volume_changed event", new Throwable()); - } - EventLogTags.writeVolumeChanged(mStreamType, oldIndex, index, mIndexMax / 10, - caller); - } - // fire changed intents for all streams, but only when the device it changed on - // is the current device - if ((index != oldIndex) && isCurrentDevice) { - // for single volume devices, only send the volume change broadcast - // on the alias stream - final int streamAlias = sStreamVolumeAlias.get( - mStreamType, /*valueIfKeyNotFound=*/-1); - if (!mIsSingleVolume || streamAlias == mStreamType) { - mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index); - mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, - oldIndex); - int extraStreamType = mStreamType; - // TODO: remove this when deprecating STREAM_BLUETOOTH_SCO - if (isStreamBluetoothSco(mStreamType)) { - mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, - AudioSystem.STREAM_BLUETOOTH_SCO); - extraStreamType = AudioSystem.STREAM_BLUETOOTH_SCO; - } else { - mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, - mStreamType); - } - mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS, - streamAlias); - - if (mStreamType == streamAlias) { - String aliasStreamIndexesString = ""; - if (!aliasStreamIndexes.isEmpty()) { - aliasStreamIndexesString = - " aliased streams: " + aliasStreamIndexes; - } - AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent( - extraStreamType, aliasStreamIndexesString, index, oldIndex)); - if (extraStreamType != mStreamType) { - AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent( - mStreamType, aliasStreamIndexesString, index, oldIndex)); + if (changed) { + // If associated to volume group, update group cache + updateVolumeGroupIndex(device, /* forceMuteState= */ false); + + oldIndex = (oldIndex + 5) / 10; + index = (index + 5) / 10; + // log base stream changes to the event log + if (sStreamVolumeAlias.get(mStreamType, /*valueIfKeyNotFound=*/-1) + == mStreamType) { + if (caller == null) { + Log.w(TAG, "No caller for volume_changed event", new Throwable()); } + EventLogTags.writeVolumeChanged( + mStreamType, oldIndex, index, mIndexMax / 10, caller); } - sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions); - if (extraStreamType != mStreamType) { - // send multiple intents in case we merged voice call and bt sco streams - mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, - mStreamType); - // do not use the options in thid case which could discard - // the previous intent - sendBroadcastToAll(mVolumeChanged, null); + // fire changed intents for all streams, but only when the device it changed + // on + // is the current device + if ((index != oldIndex) && isCurrentDevice) { + // for single volume devices, only send the volume change broadcast + // on the alias stream + final int streamAlias = + sStreamVolumeAlias.get(mStreamType, /*valueIfKeyNotFound=*/-1); + if (!mIsSingleVolume || streamAlias == mStreamType) { + mVolumeChanged.putExtra( + AudioManager.EXTRA_VOLUME_STREAM_VALUE, index); + mVolumeChanged.putExtra( + AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex); + int extraStreamType = mStreamType; + // TODO: remove this when deprecating STREAM_BLUETOOTH_SCO + if (isStreamBluetoothSco(mStreamType)) { + mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, + AudioSystem.STREAM_BLUETOOTH_SCO); + extraStreamType = AudioSystem.STREAM_BLUETOOTH_SCO; + } else { + mVolumeChanged.putExtra( + AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType); + } + mVolumeChanged.putExtra( + AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS, streamAlias); + + if (mStreamType == streamAlias) { + String aliasStreamIndexesString = ""; + if (!aliasStreamIndexes.isEmpty()) { + aliasStreamIndexesString = + " aliased streams: " + aliasStreamIndexes; + } + AudioService.sVolumeLogger.enqueue( + new VolChangedBroadcastEvent(extraStreamType, + aliasStreamIndexesString, index, oldIndex)); + if (extraStreamType != mStreamType) { + AudioService.sVolumeLogger.enqueue( + new VolChangedBroadcastEvent(mStreamType, + aliasStreamIndexesString, index, oldIndex)); + } + } + sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions); + if (extraStreamType != mStreamType) { + // send multiple intents in case we merged voice call and bt sco + // streams + mVolumeChanged.putExtra( + AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType); + // do not use the options in thid case which could discard + // the previous intent + sendBroadcastToAll(mVolumeChanged, null); + } + } } } + return changed; } } - return changed; } public int getIndex(int device) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { int index = mIndexMap.get(device, -1); if (index == -1) { // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT @@ -9788,7 +9804,7 @@ public class AudioService extends IAudioService.Stub } public @NonNull VolumeInfo getVolumeInfo(int device) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { int index = mIndexMap.get(device, -1); if (index == -1) { // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT @@ -9805,7 +9821,7 @@ public class AudioService extends IAudioService.Stub } public boolean hasIndexForDevice(int device) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { return (mIndexMap.get(device, -1) != -1); } } @@ -9837,8 +9853,8 @@ public class AudioService extends IAudioService.Stub * @param srcStream * @param caller */ - // must be sync'd on mSettingsLock before VolumeStreamState.class - @GuardedBy("VolumeStreamState.class") + // must be sync'd on mSettingsLock before mVolumeStateLock + @GuardedBy("mVolumeStateLock") public void setAllIndexes(VolumeStreamState srcStream, String caller) { if (srcStream == null || mStreamType == srcStream.mStreamType) { return; @@ -9862,8 +9878,8 @@ public class AudioService extends IAudioService.Stub } } - // must be sync'd on mSettingsLock before VolumeStreamState.class - @GuardedBy("VolumeStreamState.class") + // must be sync'd on mSettingsLock before mVolumeStateLock + @GuardedBy("mVolumeStateLock") public void setAllIndexesToMax() { for (int i = 0; i < mIndexMap.size(); i++) { mIndexMap.put(mIndexMap.keyAt(i), mIndexMax); @@ -9876,7 +9892,7 @@ public class AudioService extends IAudioService.Stub // vss.setIndex which grabs this lock after VSS.class. Locking order needs to be // preserved synchronized (mSettingsLock) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { if (mVolumeGroupState != null) { int groupIndex = (getIndex(device) + 5) / 10; if (DEBUG_VOL) { @@ -9908,7 +9924,7 @@ public class AudioService extends IAudioService.Stub */ public boolean mute(boolean state, String source) { boolean changed = false; - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { changed = mute(state, true, source); } if (changed) { @@ -9924,7 +9940,7 @@ public class AudioService extends IAudioService.Stub */ public boolean muteInternally(boolean state) { boolean changed = false; - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { if (state != mIsMutedInternally) { changed = true; mIsMutedInternally = state; @@ -9939,7 +9955,7 @@ public class AudioService extends IAudioService.Stub return changed; } - @GuardedBy("VolumeStreamState.class") + @GuardedBy("mVolumeStateLock") public boolean isFullyMuted() { return mIsMuted || mIsMutedInternally; } @@ -9960,7 +9976,7 @@ public class AudioService extends IAudioService.Stub */ public boolean mute(boolean state, boolean apply, String src) { boolean changed; - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { changed = state != mIsMuted; if (changed) { sMuteLogger.enqueue( @@ -9994,7 +10010,7 @@ public class AudioService extends IAudioService.Stub } public void doMute() { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { // If associated to volume group, update group cache updateVolumeGroupIndex(getDeviceForStream(mStreamType), /* forceMuteState= */true); @@ -10015,7 +10031,7 @@ public class AudioService extends IAudioService.Stub } public void checkFixedVolumeDevices() { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { // ignore settings for fixed volume devices: volume should always be at max or 0 if (sStreamVolumeAlias.get(mStreamType) == AudioSystem.STREAM_MUSIC) { for (int i = 0; i < mIndexMap.size(); i++) { @@ -10186,8 +10202,7 @@ public class AudioService extends IAudioService.Stub } /*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) { - - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION, SENDMSG_QUEUE, device, (isAbsoluteVolumeDevice(device) || isA2dpAbsoluteVolumeDevice(device) || AudioSystem.isLeAudioDeviceType(device) ? 1 : 0), @@ -12191,7 +12206,7 @@ public class AudioService extends IAudioService.Stub mCameraSoundForced = cameraSoundForced; if (cameraSoundForcedChanged) { if (!mIsSingleVolume) { - synchronized (VolumeStreamState.class) { + synchronized (mVolumeStateLock) { final VolumeStreamState s = getVssForStreamOrDefault( AudioSystem.STREAM_SYSTEM_ENFORCED); if (cameraSoundForced) { diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index e86c34cab88a..a6267c156fb3 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -367,9 +367,9 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, * @return */ public int setDeviceConnectionState(AudioDeviceAttributes attributes, int state, - int codecFormat) { + int codecFormat, boolean deviceSwitch) { invalidateRoutingCache(); - return AudioSystem.setDeviceConnectionState(attributes, state, codecFormat); + return AudioSystem.setDeviceConnectionState(attributes, state, codecFormat, deviceSwitch); } /** diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 922116999bc7..844e3524384d 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -26,6 +26,8 @@ import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_WATCH; +import static com.android.media.audio.Flags.optimizeBtDeviceSwitch; + import android.annotation.NonNull; import android.annotation.Nullable; import android.bluetooth.BluetoothA2dp; @@ -393,8 +395,11 @@ public class BtHelper { + "received with null profile proxy for device: " + btDevice)).printLog(TAG)); return; + } - onSetBtScoActiveDevice(btDevice); + boolean deviceSwitch = optimizeBtDeviceSwitch() + && btDevice != null && mBluetoothHeadsetDevice != null; + onSetBtScoActiveDevice(btDevice, deviceSwitch); } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); onScoAudioStateChanged(btState); @@ -814,7 +819,7 @@ public class BtHelper { if (device == null) { continue; } - onSetBtScoActiveDevice(device); + onSetBtScoActiveDevice(device, false /*deviceSwitch*/); } } else { Log.e(TAG, "onHeadsetProfileConnected: Null BluetoothAdapter"); @@ -907,7 +912,8 @@ public class BtHelper { } @GuardedBy("mDeviceBroker.mDeviceStateLock") - private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { + private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive, + boolean deviceSwitch) { if (btDevice == null) { return true; } @@ -919,12 +925,12 @@ public class BtHelper { if (isActive) { audioDevice = btHeadsetDeviceToAudioDevice(btDevice); result = mDeviceBroker.handleDeviceConnection( - audioDevice, true /*connect*/, btDevice); + audioDevice, true /*connect*/, btDevice, false /*deviceSwitch*/); } else { AudioDeviceAttributes ada = mResolvedScoAudioDevices.get(btDevice); if (ada != null) { result = mDeviceBroker.handleDeviceConnection( - ada, false /*connect*/, btDevice); + ada, false /*connect*/, btDevice, deviceSwitch); } else { // Disconnect all possible audio device types if the disconnected device type is // unknown @@ -935,7 +941,8 @@ public class BtHelper { }; for (int outDeviceType : outDeviceTypes) { result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( - outDeviceType, address, name), false /*connect*/, btDevice); + outDeviceType, address, name), false /*connect*/, btDevice, + deviceSwitch); } } } @@ -944,7 +951,7 @@ public class BtHelper { // handleDeviceConnection() && result to make sure the method get executed result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( inDevice, address, name), - isActive, btDevice) && result; + isActive, btDevice, deviceSwitch) && result; if (result) { if (isActive) { mResolvedScoAudioDevices.put(btDevice, audioDevice); @@ -961,18 +968,18 @@ public class BtHelper { } @GuardedBy("mDeviceBroker.mDeviceStateLock") - /*package */ void onSetBtScoActiveDevice(BluetoothDevice btDevice) { + /*package */ void onSetBtScoActiveDevice(BluetoothDevice btDevice, boolean deviceSwitch) { Log.i(TAG, "onSetBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice) - + " -> " + getAnonymizedAddress(btDevice)); + + " -> " + getAnonymizedAddress(btDevice) + ", deviceSwitch: " + deviceSwitch); final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; if (Objects.equals(btDevice, previousActiveDevice)) { return; } - if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) { + if (!handleBtScoActiveDeviceChange(previousActiveDevice, false, deviceSwitch)) { Log.w(TAG, "onSetBtScoActiveDevice() failed to remove previous device " + getAnonymizedAddress(previousActiveDevice)); } - if (!handleBtScoActiveDeviceChange(btDevice, true)) { + if (!handleBtScoActiveDeviceChange(btDevice, true, false /*deviceSwitch*/)) { Log.e(TAG, "onSetBtScoActiveDevice() failed to add new device " + getAnonymizedAddress(btDevice)); // set mBluetoothHeadsetDevice to null when failing to add new device diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java index b11267ef8634..79523bd02404 100644 --- a/services/core/java/com/android/server/backup/SystemBackupAgent.java +++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java @@ -69,6 +69,7 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final String SYSTEM_GENDER_HELPER = "system_gender"; private static final String DISPLAY_HELPER = "display"; private static final String INPUT_HELPER = "input"; + private static final String WEAR_BACKUP_HELPER = "wear"; // These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME // are also used in the full-backup file format, so must not change unless steps are @@ -113,7 +114,7 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final Set<String> sEligibleHelpersForNonSystemUser = SetUtils.union(sEligibleHelpersForProfileUser, Sets.newArraySet(ACCOUNT_MANAGER_HELPER, USAGE_STATS_HELPER, PREFERRED_HELPER, - SHORTCUT_MANAGER_HELPER, INPUT_HELPER)); + SHORTCUT_MANAGER_HELPER, INPUT_HELPER, WEAR_BACKUP_HELPER)); private int mUserId = UserHandle.USER_SYSTEM; private boolean mIsProfileUser = false; @@ -153,6 +154,11 @@ public class SystemBackupAgent extends BackupAgentHelper { if (com.android.hardware.input.Flags.enableBackupAndRestoreForInputGestures()) { addHelperIfEligibleForUser(INPUT_HELPER, new InputBackupHelper(mUserId)); } + + // Add Wear helper only if the device is a watch + if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { + addHelperIfEligibleForUser(WEAR_BACKUP_HELPER, new WearBackupHelper()); + } } @Override diff --git a/services/core/java/com/android/server/backup/WearBackupHelper.java b/services/core/java/com/android/server/backup/WearBackupHelper.java new file mode 100644 index 000000000000..27416b3eb2a6 --- /dev/null +++ b/services/core/java/com/android/server/backup/WearBackupHelper.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 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 + * + * https://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.backup; + +import android.annotation.Nullable; +import android.app.backup.BlobBackupHelper; + +import com.android.server.LocalServices; + +/** A {@link android.app.backup.BlobBackupHelper} for Wear */ +public class WearBackupHelper extends BlobBackupHelper { + + private static final int BLOB_VERSION = 1; + private static final String KEY_WEAR_BACKUP = "wear"; + @Nullable private final WearBackupInternal mWearBackupInternal; + + public WearBackupHelper() { + super(BLOB_VERSION, KEY_WEAR_BACKUP); + mWearBackupInternal = LocalServices.getService(WearBackupInternal.class); + } + + @Override + protected byte[] getBackupPayload(String key) { + return KEY_WEAR_BACKUP.equals(key) && mWearBackupInternal != null + ? mWearBackupInternal.getBackupPayload(getLogger()) + : null; + } + + @Override + protected void applyRestoredPayload(String key, byte[] payload) { + if (KEY_WEAR_BACKUP.equals(key) && mWearBackupInternal != null) { + mWearBackupInternal.applyRestoredPayload(payload); + } + } +} diff --git a/services/core/java/com/android/server/backup/WearBackupInternal.java b/services/core/java/com/android/server/backup/WearBackupInternal.java new file mode 100644 index 000000000000..7b4847b51df6 --- /dev/null +++ b/services/core/java/com/android/server/backup/WearBackupInternal.java @@ -0,0 +1,32 @@ +/* + * Copyright 2024 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 + * + * https://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.backup; + +import android.app.backup.BackupRestoreEventLogger; + +import com.android.internal.annotations.Keep; + +/** A local service internal for Wear OS handle backup/restore */ +@Keep +public interface WearBackupInternal { + + /** Gets the backup payload */ + byte[] getBackupPayload(BackupRestoreEventLogger logger); + + /** Applies the restored payload */ + void applyRestoredPayload(byte[] payload); +} diff --git a/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java index 000ee5446962..9f364677705e 100644 --- a/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java +++ b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java @@ -20,12 +20,16 @@ import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import android.annotation.NonNull; import android.hardware.SensorPrivacyManager; +import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraManager; +import android.util.Log; import java.util.concurrent.ConcurrentHashMap; public class BiometricCameraManagerImpl implements BiometricCameraManager { + private static final String TAG = "BiometricCameraManager"; + private final CameraManager mCameraManager; private final SensorPrivacyManager mSensorPrivacyManager; private final ConcurrentHashMap<String, Boolean> mIsCameraAvailable = new ConcurrentHashMap<>(); @@ -52,12 +56,18 @@ public class BiometricCameraManagerImpl implements BiometricCameraManager { @Override public boolean isAnyCameraUnavailable() { - for (String cameraId : mIsCameraAvailable.keySet()) { - if (!mIsCameraAvailable.get(cameraId)) { - return true; + try { + for (String cameraId : mCameraManager.getCameraIdList()) { + if (!mIsCameraAvailable.getOrDefault(cameraId, true)) { + return true; + } } + return false; + } catch (CameraAccessException e) { + Log.e(TAG, "Camera exception thrown when trying to determine availability: ", e); + //If face HAL is unable to get access to a camera, it will return an error. + return false; } - return false; } @Override diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index d412277d2605..f5284a3ed589 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -211,4 +211,20 @@ public abstract class VirtualDeviceManagerInternal { */ public abstract @NonNull VirtualDeviceManager.VirtualDevice createVirtualDevice( @NonNull VirtualDeviceParams params); + + /** + * Returns the details of the virtual device with the given ID, if any. + * + * <p>The returned object is a read-only representation of the virtual device that expose its + * properties.</p> + * + * <p>Note that if the virtual device is closed and becomes invalid, the returned object will + * not be updated and may contain stale values. Use a {@link VirtualDeviceListener} for real + * time updates of the availability of virtual devices.</p> + * + * @return the virtual device with the requested ID, or {@code null} if no such device exists or + * it has already been closed. + */ + @Nullable + public abstract VirtualDevice getVirtualDevice(int deviceId); } diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 88f5c81231b8..c41b8db1ce75 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -333,7 +333,7 @@ public class AutomaticBrightnessController { int ambientLightHorizonLong, float userLux, float userNits, DisplayManagerFlags displayManagerFlags) { mInjector = injector; - mClock = injector.createClock(displayManagerFlags.offloadControlsDozeAutoBrightness()); + mClock = injector.createClock(); mContext = context; mCallbacks = callbacks; mSensorManager = sensorManager; @@ -1402,8 +1402,7 @@ public class AutomaticBrightnessController { public void onSensorChanged(SensorEvent event) { if (mLightSensorEnabled) { // The time received from the sensor is in nano seconds, hence changing it to ms - final long time = (mDisplayManagerFlags.offloadControlsDozeAutoBrightness()) - ? TimeUnit.NANOSECONDS.toMillis(event.timestamp) : mClock.uptimeMillis(); + final long time = TimeUnit.NANOSECONDS.toMillis(event.timestamp); final float lux = event.values[0]; handleLightSensorEvent(time, lux); } @@ -1616,20 +1615,13 @@ public class AutomaticBrightnessController { } private static class RealClock implements Clock { - private final boolean mOffloadControlsDozeBrightness; - - RealClock(boolean offloadControlsDozeBrightness) { - mOffloadControlsDozeBrightness = offloadControlsDozeBrightness; - } - @Override public long uptimeMillis() { return SystemClock.uptimeMillis(); } public long getSensorEventScaleTime() { - return (mOffloadControlsDozeBrightness) - ? SystemClock.elapsedRealtime() : uptimeMillis(); + return SystemClock.elapsedRealtime(); } } @@ -1638,8 +1630,8 @@ public class AutomaticBrightnessController { return BackgroundThread.getHandler(); } - Clock createClock(boolean offloadControlsDozeBrightness) { - return new RealClock(offloadControlsDozeBrightness); + Clock createClock() { + return new RealClock(); } } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index b6a3f4041b13..258c95582e3a 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -812,7 +812,7 @@ public final class DisplayManagerService extends SystemService { handleMinimalPostProcessingAllowedSettingChange(); if (mFlags.isDisplayContentModeManagementEnabled()) { - updateMirrorBuiltInDisplaySettingLocked(); + updateMirrorBuiltInDisplaySettingLocked(/*shouldSendDisplayChangeEvent=*/ true); } final UserManager userManager = getUserManager(); @@ -868,7 +868,7 @@ public final class DisplayManagerService extends SystemService { updateHdrConversionModeSettingsLocked(); } if (mFlags.isDisplayContentModeManagementEnabled()) { - updateMirrorBuiltInDisplaySettingLocked(); + updateMirrorBuiltInDisplaySettingLocked(/*shouldSendDisplayChangeEvent=*/ false); } } @@ -1237,8 +1237,11 @@ public final class DisplayManagerService extends SystemService { } if (Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY).equals(uri)) { - if (mFlags.isDisplayContentModeManagementEnabled()) { - updateMirrorBuiltInDisplaySettingLocked(); + synchronized (mSyncRoot) { + if (mFlags.isDisplayContentModeManagementEnabled()) { + updateMirrorBuiltInDisplaySettingLocked(/*shouldSendDisplayChangeEvent=*/ + true); + } } return; } @@ -1258,18 +1261,19 @@ public final class DisplayManagerService extends SystemService { 1, UserHandle.USER_CURRENT) != 0); } - private void updateMirrorBuiltInDisplaySettingLocked() { - synchronized (mSyncRoot) { - ContentResolver resolver = mContext.getContentResolver(); - final boolean mirrorBuiltInDisplay = Settings.Secure.getIntForUser(resolver, - MIRROR_BUILT_IN_DISPLAY, 0, UserHandle.USER_CURRENT) != 0; - if (mMirrorBuiltInDisplay == mirrorBuiltInDisplay) { - return; - } - mMirrorBuiltInDisplay = mirrorBuiltInDisplay; - if (mFlags.isDisplayContentModeManagementEnabled()) { - mLogicalDisplayMapper.forEachLocked(this::updateCanHostTasksIfNeededLocked); - } + private void updateMirrorBuiltInDisplaySettingLocked(boolean shouldSendDisplayChangeEvent) { + ContentResolver resolver = mContext.getContentResolver(); + final boolean mirrorBuiltInDisplay = Settings.Secure.getIntForUser(resolver, + MIRROR_BUILT_IN_DISPLAY, 0, UserHandle.USER_CURRENT) != 0; + if (mMirrorBuiltInDisplay == mirrorBuiltInDisplay) { + return; + } + mMirrorBuiltInDisplay = mirrorBuiltInDisplay; + if (mFlags.isDisplayContentModeManagementEnabled()) { + mLogicalDisplayMapper.forEachLocked(logicalDisplay -> { + updateCanHostTasksIfNeededLocked(logicalDisplay, + shouldSendDisplayChangeEvent); + }); } } @@ -2380,7 +2384,7 @@ public final class DisplayManagerService extends SystemService { new BrightnessPair(brightnessDefault, brightnessDefault)); if (mFlags.isDisplayContentModeManagementEnabled()) { - updateCanHostTasksIfNeededLocked(display); + updateCanHostTasksIfNeededLocked(display, /*shouldSendDisplayChangeEvent=*/ false); } DisplayManagerGlobal.invalidateLocalDisplayInfoCaches(); @@ -2587,6 +2591,11 @@ public final class DisplayManagerService extends SystemService { sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_STATE_CHANGED); } + private void handleLogicalDisplayCommittedStateChangedLocked(@NonNull LogicalDisplay display) { + sendDisplayEventIfEnabledLocked(display, + DisplayManagerGlobal.EVENT_DISPLAY_COMMITTED_STATE_CHANGED); + } + private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) { mDisplayModeDirector.defaultDisplayDeviceUpdated(display.getPrimaryDisplayDeviceLocked() .mDisplayDeviceConfig); @@ -2609,7 +2618,8 @@ public final class DisplayManagerService extends SystemService { // Blank or unblank the display immediately to match the state requested // by the display power controller (if known). DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); - if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) { + if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0 + || android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()) { final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); if (display == null) { return null; @@ -2697,8 +2707,9 @@ public final class DisplayManagerService extends SystemService { } } - private void updateCanHostTasksIfNeededLocked(LogicalDisplay display) { - if (display.setCanHostTasksLocked(!mMirrorBuiltInDisplay)) { + private void updateCanHostTasksIfNeededLocked(LogicalDisplay display, + boolean shouldSendDisplayChangeEvent) { + if (display.setCanHostTasksLocked(!mMirrorBuiltInDisplay) && shouldSendDisplayChangeEvent) { sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); } @@ -4165,6 +4176,9 @@ public final class DisplayManagerService extends SystemService { case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_STATE_CHANGED: handleLogicalDisplayStateChangedLocked(display); break; + case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED: + handleLogicalDisplayCommittedStateChangedLocked(display); + break; } } @@ -4419,6 +4433,9 @@ public final class DisplayManagerService extends SystemService { case DisplayManagerGlobal.EVENT_DISPLAY_STATE_CHANGED: return (mask & DisplayManagerGlobal .INTERNAL_EVENT_FLAG_DISPLAY_STATE) != 0; + case DisplayManagerGlobal.EVENT_DISPLAY_COMMITTED_STATE_CHANGED: + return (mask & DisplayManagerGlobal + .INTERNAL_EVENT_FLAG_DISPLAY_COMMITTED_STATE_CHANGED) != 0; default: // This should never happen. Slog.e(TAG, "Unknown display event " + event); @@ -5563,7 +5580,9 @@ public final class DisplayManagerService extends SystemService { final DisplayDevice displayDevice = mLogicalDisplayMapper.getDisplayLocked( id).getPrimaryDisplayDeviceLocked(); final int flags = displayDevice.getDisplayDeviceInfoLocked().flags; - if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) { + if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0 + || android.companion.virtualdevice.flags.Flags + .correctVirtualDisplayPowerState()) { final DisplayPowerController displayPowerController = mDisplayPowerControllers.get(id); if (displayPowerController != null) { diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 02db051dff57..872f33484951 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -91,6 +91,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { public static final int LOGICAL_DISPLAY_EVENT_DISCONNECTED = 1 << 8; public static final int LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED = 1 << 9; public static final int LOGICAL_DISPLAY_EVENT_STATE_CHANGED = 1 << 10; + public static final int LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED = 1 << 11; + public static final int DISPLAY_GROUP_EVENT_ADDED = 1; public static final int DISPLAY_GROUP_EVENT_CHANGED = 2; @@ -810,7 +812,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { int logicalDisplayEventMask = mLogicalDisplaysToUpdate .get(displayId, LOGICAL_DISPLAY_EVENT_BASE); boolean hasBasicInfoChanged = - !mTempDisplayInfo.equals(newDisplayInfo, /* compareRefreshRate */ false); + !mTempDisplayInfo.equals(newDisplayInfo, /* compareOnlyBasicChanges */ true); // The display is no longer valid and needs to be removed. if (!display.isValidLocked()) { // Remove from group @@ -930,6 +932,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_BASIC_CHANGED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_STATE_CHANGED); + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CONNECTED); @@ -961,6 +964,11 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { && mTempDisplayInfo.state != newDisplayInfo.state) { mask |= LOGICAL_DISPLAY_EVENT_STATE_CHANGED; } + + if (mFlags.isCommittedStateSeparateEventEnabled() + && mTempDisplayInfo.committedState != newDisplayInfo.committedState) { + mask |= LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED; + } return mask; } /** @@ -1360,6 +1368,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return "disconnected"; case LOGICAL_DISPLAY_EVENT_STATE_CHANGED: return "state_changed"; + case LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED: + return "committed_state_changed"; case LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED: return "refresh_rate_changed"; case LOGICAL_DISPLAY_EVENT_BASIC_CHANGED: diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 4779b690adfb..e7939bb50ece 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -371,7 +371,15 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mCallback = callback; mProjection = projection; mMediaProjectionCallback = mediaProjectionCallback; - mDisplayState = Display.STATE_ON; + if (android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()) { + // The display's power state depends on the power state of the state of its + // display / power group, which we don't know here. Initializing to UNKNOWN allows + // the first call to requestDisplayStateLocked() to set the correct state. + // This also triggers VirtualDisplay.Callback to tell the owner the initial state. + mDisplayState = Display.STATE_UNKNOWN; + } else { + mDisplayState = Display.STATE_ON; + } mPendingChanges |= PENDING_SURFACE_CHANGE; mDisplayIdToMirror = virtualDisplayConfig.getDisplayIdToMirror(); mIsWindowManagerMirroring = virtualDisplayConfig.isWindowManagerMirroringEnabled(); @@ -564,14 +572,23 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mInfo.yDpi = mDensityDpi; mInfo.presentationDeadlineNanos = 1000000000L / (int) getRefreshRate(); // 1 frame mInfo.flags = 0; - if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) { - mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE - | DisplayDeviceInfo.FLAG_NEVER_BLANK; - } - if ((mFlags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) { - mInfo.flags &= ~DisplayDeviceInfo.FLAG_NEVER_BLANK; + if (android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()) { + if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) { + mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE; + } + if ((mFlags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0) { + mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY; + } } else { - mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY; + if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) { + mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE + | DisplayDeviceInfo.FLAG_NEVER_BLANK; + } + if ((mFlags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) { + mInfo.flags &= ~DisplayDeviceInfo.FLAG_NEVER_BLANK; + } else { + mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY; + } } if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) { mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP; diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java index 2c6f37448735..6510441ba28f 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java @@ -291,8 +291,7 @@ public class DisplayBrightnessStrategySelector { void setAllowAutoBrightnessWhileDozing( DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) { mAllowAutoBrightnessWhileDozing = mAllowAutoBrightnessWhileDozingConfig; - if (mDisplayManagerFlags.offloadControlsDozeAutoBrightness() - && mDisplayManagerFlags.isDisplayOffloadEnabled() + if (mDisplayManagerFlags.isDisplayOffloadEnabled() && displayOffloadSession != null) { mAllowAutoBrightnessWhileDozing &= displayOffloadSession.allowAutoBrightnessInDoze(); } diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index bc5d90599b41..c3057ded66eb 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -156,11 +156,6 @@ public class DisplayManagerFlags { Flags.FLAG_DOZE_BRIGHTNESS_FLOAT, Flags::dozeBrightnessFloat); - private final FlagState mOffloadControlsDozeAutoBrightness = new FlagState( - Flags.FLAG_OFFLOAD_CONTROLS_DOZE_AUTO_BRIGHTNESS, - Flags::offloadControlsDozeAutoBrightness - ); - private final FlagState mPeakRefreshRatePhysicalLimit = new FlagState( Flags.FLAG_ENABLE_PEAK_REFRESH_RATE_PHYSICAL_LIMIT, Flags::enablePeakRefreshRatePhysicalLimit @@ -280,6 +275,11 @@ public class DisplayManagerFlags { Flags::refreshRateEventForForegroundApps ); + private final FlagState mCommittedStateSeparateEvent = new FlagState( + Flags.FLAG_COMMITTED_STATE_SEPARATE_EVENT, + Flags::committedStateSeparateEvent + ); + /** * @return {@code true} if 'port' is allowed in display layout configuration file. */ @@ -435,13 +435,6 @@ public class DisplayManagerFlags { return mDozeBrightnessFloat.isEnabled(); } - /** - * @return Whether DisplayOffload should control auto-brightness in doze - */ - public boolean offloadControlsDozeAutoBrightness() { - return mOffloadControlsDozeAutoBrightness.isEnabled(); - } - public boolean isPeakRefreshRatePhysicalLimitEnabled() { return mPeakRefreshRatePhysicalLimit.isEnabled(); } @@ -603,6 +596,14 @@ public class DisplayManagerFlags { } /** + * @return {@code true} if the flag for having a separate event for display's committed state + * is enabled + */ + public boolean isCommittedStateSeparateEventEnabled() { + return mCommittedStateSeparateEvent.isEnabled(); + } + + /** * dumps all flagstates * @param pw printWriter */ @@ -634,7 +635,6 @@ public class DisplayManagerFlags { pw.println(" " + mResolutionBackupRestore); pw.println(" " + mUseFusionProxSensor); pw.println(" " + mDozeBrightnessFloat); - pw.println(" " + mOffloadControlsDozeAutoBrightness); pw.println(" " + mPeakRefreshRatePhysicalLimit); pw.println(" " + mIgnoreAppPreferredRefreshRate); pw.println(" " + mSynthetic60hzModes); @@ -659,6 +659,7 @@ public class DisplayManagerFlags { pw.println(" " + mBaseDensityForExternalDisplays); pw.println(" " + mFramerateOverrideTriggersRrCallbacks); pw.println(" " + mRefreshRateEventForForegroundApps); + pw.println(" " + mCommittedStateSeparateEvent); } private static class FlagState { diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 8211febade60..f5451307afb7 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -255,17 +255,6 @@ flag { } flag { - name: "offload_controls_doze_auto_brightness" - namespace: "display_manager" - description: "Allows the registered DisplayOffloader to control if auto-brightness is used in doze" - bug: "327392714" - is_fixed_read_only: true - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "enable_peak_refresh_rate_physical_limit" namespace: "display_manager" description: "Flag for adding physical refresh rate limit if smooth display setting is on " @@ -456,9 +445,8 @@ flag { flag { name: "enable_display_content_mode_management" namespace: "lse_desktop_experience" - description: "Enable switching the content mode of connected displays between mirroring and extened. Also change the default content mode to extended mode." + description: "Enable switching the content mode of connected displays between mirroring and extended. Also change the default content mode to extended mode." bug: "378385869" - is_fixed_read_only: true } flag { @@ -509,3 +497,14 @@ flag { bug: "293651324" is_fixed_read_only: false } + +flag { + name: "committed_state_separate_event" + namespace: "display_manager" + description: "Move Display committed state into a separate event" + bug: "342192387" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java index 94842041af82..ab86433ca50d 100644 --- a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java +++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java @@ -58,9 +58,9 @@ public interface AudioDeviceVolumeManagerWrapper { void setDeviceAbsoluteVolumeBehavior( @NonNull AudioDeviceAttributes device, @NonNull VolumeInfo volume, + boolean handlesVolumeAdjustment, @NonNull @CallbackExecutor Executor executor, - @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener, - boolean handlesVolumeAdjustment); + @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener); /** * Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeAdjustOnlyBehavior( @@ -69,7 +69,7 @@ public interface AudioDeviceVolumeManagerWrapper { void setDeviceAbsoluteVolumeAdjustOnlyBehavior( @NonNull AudioDeviceAttributes device, @NonNull VolumeInfo volume, + boolean handlesVolumeAdjustment, @NonNull @CallbackExecutor Executor executor, - @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener, - boolean handlesVolumeAdjustment); + @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener); } diff --git a/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java index ff99ace38ef0..10cbb00d2398 100644 --- a/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java +++ b/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java @@ -61,21 +61,21 @@ public class DefaultAudioDeviceVolumeManagerWrapper public void setDeviceAbsoluteVolumeBehavior( @NonNull AudioDeviceAttributes device, @NonNull VolumeInfo volume, + boolean handlesVolumeAdjustment, @NonNull @CallbackExecutor Executor executor, - @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener, - boolean handlesVolumeAdjustment) { - mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeBehavior(device, volume, executor, - vclistener, handlesVolumeAdjustment); + @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener) { + mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeBehavior(device, volume, + handlesVolumeAdjustment, executor, vclistener); } @Override public void setDeviceAbsoluteVolumeAdjustOnlyBehavior( @NonNull AudioDeviceAttributes device, @NonNull VolumeInfo volume, + boolean handlesVolumeAdjustment, @NonNull @CallbackExecutor Executor executor, - @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener, - boolean handlesVolumeAdjustment) { + @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener) { mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeAdjustOnlyBehavior(device, volume, - executor, vclistener, handlesVolumeAdjustment); + handlesVolumeAdjustment, executor, vclistener); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 3d6d34bf9911..3cb21c3e2697 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -1404,6 +1404,9 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (connected) { if (mArcEstablished) { enableAudioReturnChannel(true); + } else { + HdmiLogger.debug("Restart ARC again"); + onNewAvrAdded(getAvrDeviceInfo()); } } else { enableAudioReturnChannel(false); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 89f0d0edbf2b..6d973ac8d1b5 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -4798,15 +4798,15 @@ public class HdmiControlService extends SystemService { Slog.d(TAG, "Enabling absolute volume behavior"); for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) { getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior( - device, volumeInfo, mServiceThreadExecutor, - mAbsoluteVolumeChangedListener, true); + device, volumeInfo, true, mServiceThreadExecutor, + mAbsoluteVolumeChangedListener); } } else if (tv() != null) { Slog.d(TAG, "Enabling adjust-only absolute volume behavior"); for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) { getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeAdjustOnlyBehavior( - device, volumeInfo, mServiceThreadExecutor, - mAbsoluteVolumeChangedListener, true); + device, volumeInfo, true, mServiceThreadExecutor, + mAbsoluteVolumeChangedListener); } } diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java index 977c029f3a29..d71c8a1056d9 100644 --- a/services/core/java/com/android/server/input/InputGestureManager.java +++ b/services/core/java/com/android/server/input/InputGestureManager.java @@ -146,11 +146,6 @@ final class InputGestureManager { KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL ), createKeyGesture( - KeyEvent.KEYCODE_N, - KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES - ), - createKeyGesture( KeyEvent.KEYCODE_S, KeyEvent.META_META_ON, KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java index 87f693cc7291..1ace41cba364 100644 --- a/services/core/java/com/android/server/input/InputManagerInternal.java +++ b/services/core/java/com/android/server/input/InputManagerInternal.java @@ -344,4 +344,42 @@ public abstract class InputManagerInternal { */ public abstract void applyBackupPayload(Map<Integer, byte[]> payload, int userId) throws XmlPullParserException, IOException; + + /** + * An interface for filtering pointer motion event before cursor position is determined. + * <p> + * Different from {@code android.view.InputFilter}, this filter can filter motion events at + * an early stage of the input pipeline, but only called for pointer's relative motion events. + * Unless the user really needs to filter events before the cursor position in the display is + * determined, use {@code android.view.InputFilter} instead. + */ + public interface AccessibilityPointerMotionFilter { + /** + * Called everytime pointer's relative motion event happens. + * The returned dx and dy will be used to move the cursor in the display. + * <p> + * This call happens on the input hot path and it is extremely performance sensitive. It + * also must not call back into native code. + * + * @param dx delta x of the event in pixels. + * @param dy delta y of the event in pixels. + * @param currentX the cursor x coordinate on the screen before the motion event. + * @param currentY the cursor y coordinate on the screen before the motion event. + * @param displayId the display ID of the current cursor. + * @return an array of length 2, delta x and delta y after filtering the motion. The delta + * values are in pixels and must be between 0 and original delta. + */ + @NonNull + float[] filterPointerMotionEvent(float dx, float dy, float currentX, float currentY, + int displayId); + } + + /** + * Registers an {@code AccessibilityCursorFilter}. + * + * @param filter The filter to register. If a filter is already registered, the old filter is + * unregistered. {@code null} unregisters the filter that is already registered. + */ + public abstract void registerAccessibilityPointerMotionFilter( + @Nullable AccessibilityPointerMotionFilter filter); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 8624f4230e9c..c2fecf283a34 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -25,8 +25,8 @@ import static android.view.KeyEvent.KEYCODE_UNKNOWN; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static com.android.hardware.input.Flags.enableCustomizableInputGestures; -import static com.android.hardware.input.Flags.touchpadVisualizer; import static com.android.hardware.input.Flags.keyEventActivityDetection; +import static com.android.hardware.input.Flags.touchpadVisualizer; import static com.android.hardware.input.Flags.useKeyGestureEventHandler; import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER; @@ -143,6 +143,7 @@ import com.android.internal.policy.KeyInterceptionInfo; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.DisplayThread; +import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.Watchdog; @@ -193,15 +194,11 @@ public class InputManagerService extends IInputManager.Stub private static final int MSG_SYSTEM_READY = 5; private static final int DEFAULT_VIBRATION_MAGNITUDE = 192; - private static final AdditionalDisplayInputProperties - DEFAULT_ADDITIONAL_DISPLAY_INPUT_PROPERTIES = new AdditionalDisplayInputProperties(); private final NativeInputManagerService mNative; private final Context mContext; private final InputManagerHandler mHandler; - @UserIdInt - private int mCurrentUserId = UserHandle.USER_SYSTEM; private DisplayManagerInternal mDisplayManagerInternal; private WindowManagerInternal mWindowManagerInternal; @@ -289,7 +286,7 @@ public class InputManagerService extends IInputManager.Stub final Object mKeyEventActivityLock = new Object(); @GuardedBy("mKeyEventActivityLock") - private List<IKeyEventActivityListener> mKeyEventActivityListenersToNotify = + private final List<IKeyEventActivityListener> mKeyEventActivityListenersToNotify = new ArrayList<>(); // Rate limit for key event activity detection. Prevent the listener from being notified @@ -460,16 +457,26 @@ public class InputManagerService extends IInputManager.Stub private boolean mShowKeyPresses = false; private boolean mShowRotaryInput = false; + /** + * A lock for the accessibility pointer motion filter. Don't call native methods while holding + * this lock. + */ + private final Object mAccessibilityPointerMotionFilterLock = new Object(); + private InputManagerInternal.AccessibilityPointerMotionFilter + mAccessibilityPointerMotionFilter = null; + /** Point of injection for test dependencies. */ @VisibleForTesting static class Injector { private final Context mContext; private final Looper mLooper; + private final Looper mIoLooper; private final UEventManager mUEventManager; - Injector(Context context, Looper looper, UEventManager uEventManager) { + Injector(Context context, Looper looper, Looper ioLooper, UEventManager uEventManager) { mContext = context; mLooper = looper; + mIoLooper = ioLooper; mUEventManager = uEventManager; } @@ -481,6 +488,10 @@ public class InputManagerService extends IInputManager.Stub return mLooper; } + Looper getIoLooper() { + return mIoLooper; + } + UEventManager getUEventManager() { return mUEventManager; } @@ -501,8 +512,8 @@ public class InputManagerService extends IInputManager.Stub } public InputManagerService(Context context) { - this(new Injector(context, DisplayThread.get().getLooper(), new UEventManager() {}), - context.getSystemService(PermissionEnforcer.class)); + this(new Injector(context, DisplayThread.get().getLooper(), IoThread.get().getLooper(), + new UEventManager() {}), context.getSystemService(PermissionEnforcer.class)); } @VisibleForTesting @@ -528,7 +539,7 @@ public class InputManagerService extends IInputManager.Stub mStickyModifierStateController = new StickyModifierStateController(); mInputDataStore = new InputDataStore(); mKeyGestureController = new KeyGestureController(mContext, injector.getLooper(), - mInputDataStore); + injector.getIoLooper(), mInputDataStore); mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(), mNative); mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper()); @@ -2593,6 +2604,23 @@ public class InputManagerService extends IInputManager.Stub // Native callback. @SuppressWarnings("unused") + final float[] filterPointerMotion(float dx, float dy, float currentX, float currentY, + int displayId) { + // This call happens on the input hot path and it is extremely performance sensitive. + // This must not call back into native code. This is called while the + // PointerChoreographer's lock is held. + synchronized (mAccessibilityPointerMotionFilterLock) { + if (mAccessibilityPointerMotionFilter == null) { + throw new IllegalStateException( + "filterCursor is invoked but no callback is registered."); + } + return mAccessibilityPointerMotionFilter.filterPointerMotionEvent(dx, dy, currentX, + currentY, displayId); + } + } + + // Native callback. + @SuppressWarnings("unused") @VisibleForTesting public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { notifyKeyActivityListeners(event); @@ -3215,7 +3243,6 @@ public class InputManagerService extends IInputManager.Stub } private void handleCurrentUserChanged(@UserIdInt int userId) { - mCurrentUserId = userId; mKeyGestureController.setCurrentUserId(userId); } @@ -3828,6 +3855,12 @@ public class InputManagerService extends IInputManager.Stub payload.get(BACKUP_CATEGORY_INPUT_GESTURES), userId); } } + + @Override + public void registerAccessibilityPointerMotionFilter( + AccessibilityPointerMotionFilter filter) { + InputManagerService.this.registerAccessibilityPointerMotionFilter(filter); + } } @Override @@ -4014,6 +4047,26 @@ public class InputManagerService extends IInputManager.Stub mPointerIconCache.setAccessibilityScaleFactor(displayId, scaleFactor); } + void registerAccessibilityPointerMotionFilter( + InputManagerInternal.AccessibilityPointerMotionFilter filter) { + // `#filterPointerMotion` expects that when it's called, `mAccessibilityPointerMotionFilter` + // is not null. + // Also, to avoid potential lock contention, we shouldn't call native method while holding + // the lock here. Native code calls `#filterPointerMotion` while PointerChoreographer's + // lock is held. + // Thus, we must set filter before we enable the filter in native, and reset the filter + // after we disable the filter. + // This also ensures the previously installed filter isn't called after the filter is + // updated. + mNative.setAccessibilityPointerMotionFilterEnabled(false); + synchronized (mAccessibilityPointerMotionFilterLock) { + mAccessibilityPointerMotionFilter = filter; + } + if (filter != null) { + mNative.setAccessibilityPointerMotionFilterEnabled(true); + } + } + interface KeyboardBacklightControllerInterface { default void incrementKeyboardBacklight(int deviceId) {} default void decrementKeyboardBacklight(int deviceId) {} diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java index 5770a09e3b92..fba0b0453bfb 100644 --- a/services/core/java/com/android/server/input/KeyGestureController.java +++ b/services/core/java/com/android/server/input/KeyGestureController.java @@ -121,6 +121,7 @@ final class KeyGestureController { private final Context mContext; private final Handler mHandler; + private final Handler mIoHandler; private final int mSystemPid; private final KeyCombinationManager mKeyCombinationManager; private final SettingsObserver mSettingsObserver; @@ -171,9 +172,11 @@ final class KeyGestureController { private final boolean mVisibleBackgroundUsersEnabled = isVisibleBackgroundUsersEnabled(); - KeyGestureController(Context context, Looper looper, InputDataStore inputDataStore) { + KeyGestureController(Context context, Looper looper, Looper ioLooper, + InputDataStore inputDataStore) { mContext = context; mHandler = new Handler(looper, this::handleMessage); + mIoHandler = new Handler(ioLooper, this::handleIoMessage); mSystemPid = Process.myPid(); mKeyGestureHandlerRecords = new TreeMap<>((p1, p2) -> { if (Objects.equals(p1, p2)) { @@ -458,7 +461,7 @@ final class KeyGestureController { userId = mCurrentUserId; } // Load the system user's input gestures. - mHandler.obtainMessage(MSG_LOAD_CUSTOM_GESTURES, userId).sendToTarget(); + mIoHandler.obtainMessage(MSG_LOAD_CUSTOM_GESTURES, userId).sendToTarget(); } public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { @@ -1032,7 +1035,7 @@ final class KeyGestureController { synchronized (mUserLock) { mCurrentUserId = userId; } - mHandler.obtainMessage(MSG_LOAD_CUSTOM_GESTURES, userId).sendToTarget(); + mIoHandler.obtainMessage(MSG_LOAD_CUSTOM_GESTURES, userId).sendToTarget(); } @MainThread @@ -1073,6 +1076,12 @@ final class KeyGestureController { AidlKeyGestureEvent event = (AidlKeyGestureEvent) msg.obj; notifyKeyGestureEvent(event); break; + } + return true; + } + + private boolean handleIoMessage(Message msg) { + switch (msg.what) { case MSG_PERSIST_CUSTOM_GESTURES: { final int userId = (Integer) msg.obj; persistInputGestures(userId); @@ -1083,7 +1092,6 @@ final class KeyGestureController { loadInputGestures(userId); break; } - } return true; } @@ -1144,7 +1152,7 @@ final class KeyGestureController { final int result = mInputGestureManager.addCustomInputGesture(userId, new InputGestureData(inputGestureData)); if (result == InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS) { - mHandler.obtainMessage(MSG_PERSIST_CUSTOM_GESTURES, userId).sendToTarget(); + mIoHandler.obtainMessage(MSG_PERSIST_CUSTOM_GESTURES, userId).sendToTarget(); } return result; } @@ -1156,7 +1164,7 @@ final class KeyGestureController { final int result = mInputGestureManager.removeCustomInputGesture(userId, new InputGestureData(inputGestureData)); if (result == InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS) { - mHandler.obtainMessage(MSG_PERSIST_CUSTOM_GESTURES, userId).sendToTarget(); + mIoHandler.obtainMessage(MSG_PERSIST_CUSTOM_GESTURES, userId).sendToTarget(); } return result; } @@ -1165,7 +1173,7 @@ final class KeyGestureController { public void removeAllCustomInputGestures(@UserIdInt int userId, @Nullable InputGestureData.Filter filter) { mInputGestureManager.removeAllCustomInputGestures(userId, filter); - mHandler.obtainMessage(MSG_PERSIST_CUSTOM_GESTURES, userId).sendToTarget(); + mIoHandler.obtainMessage(MSG_PERSIST_CUSTOM_GESTURES, userId).sendToTarget(); } @BinderThread diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index f34338a397db..32409d39db3b 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -315,6 +315,16 @@ interface NativeInputManagerService { */ boolean setKernelWakeEnabled(int deviceId, boolean enabled); + /** + * Set whether the accessibility pointer motion filter is enabled. + * <p> + * Once enabled, {@link InputManagerService#filterPointerMotion} is called for evety motion + * event from pointer devices. + * + * @param enabled {@code true} if the filter is enabled, {@code false} otherwise. + */ + void setAccessibilityPointerMotionFilterEnabled(boolean enabled); + /** The native implementation of InputManagerService methods. */ class NativeImpl implements NativeInputManagerService { /** Pointer to native input manager service object, used by native code. */ @@ -628,5 +638,8 @@ interface NativeInputManagerService { @Override public native boolean setKernelWakeEnabled(int deviceId, boolean enabled); + + @Override + public native void setAccessibilityPointerMotionFilterEnabled(boolean enabled); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 484b47022f04..dfdd9e54fe2e 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -365,7 +365,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return mCurrentImeUserId; } - /** + /** * Figures out the target IME user ID associated with the given {@code displayId}. * * @param displayId the display ID to be queried about @@ -649,12 +649,25 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. visibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard( accessibilitySoftKeyboardSetting); if (visibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) { - hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, - 0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE, userId); + if (Flags.refactorInsetsController()) { + final var statsToken = createStatsTokenForFocusedClient(false /* show */, + SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE, userId); + setImeVisibilityOnFocusedWindowClient(false, userData, statsToken); + } else { + hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, + 0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE, + userId); + } } else if (isShowRequestedForCurrentWindow(userId)) { - showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, - InputMethodManager.SHOW_IMPLICIT, - SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, userId); + if (Flags.refactorInsetsController()) { + final var statsToken = createStatsTokenForFocusedClient(true /* show */, + SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, userId); + setImeVisibilityOnFocusedWindowClient(true, userData, statsToken); + } else { + showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, + InputMethodManager.SHOW_IMPLICIT, + SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, userId); + } } break; } @@ -1319,8 +1332,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Do not reset the default (current) IME when it is a 3rd-party IME String selectedMethodId = bindingController.getSelectedMethodId(); final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); - if (selectedMethodId != null && settings.getMethodMap().get(selectedMethodId) != null - && !settings.getMethodMap().get(selectedMethodId).isSystem()) { + final InputMethodInfo selectedImi = settings.getMethodMap().get(selectedMethodId); + if (selectedImi != null && !selectedImi.isSystem()) { return; } final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes( @@ -3472,7 +3485,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. || (windowPerceptible != null && windowPerceptible == perceptible)) { return; } - mFocusedWindowPerceptible.put(windowToken, windowPerceptible); + mFocusedWindowPerceptible.put(windowToken, perceptible); updateSystemUiLocked(userId); } }); diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java index 2ea61171b9e1..1a29150cd40c 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java @@ -374,6 +374,34 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub } } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(mEndpointInfo).append(", "); + sb.append("package: ").append(mPackageName).append(", "); + synchronized (mWakeLock) { + sb.append("wakelock: ").append(mWakeLock); + } + synchronized (mOpenSessionLock) { + if (mSessionInfoMap.size() != 0) { + sb.append(System.lineSeparator()); + sb.append(" sessions: "); + sb.append(System.lineSeparator()); + } + for (int i = 0; i < mSessionInfoMap.size(); i++) { + int id = mSessionInfoMap.keyAt(i); + int count = i + 1; + sb.append( + " " + count + ". id=" + + id + + ", remote:" + + mSessionInfoMap.get(id).getRemoteEndpointInfo()); + sb.append(System.lineSeparator()); + } + } + return sb.toString(); + } + /** * Registers this endpoints with the Context Hub HAL. * diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java index 06aeb62a28b8..30bb8f3fa188 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java @@ -16,6 +16,7 @@ package com.android.server.location.contexthub; +import android.annotation.IntDef; import android.content.Context; import android.hardware.contexthub.ContextHubInfo; import android.hardware.contexthub.EndpointInfo; @@ -34,8 +35,11 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -105,6 +109,48 @@ import java.util.function.Consumer; /** The interface for endpoint communication (retrieved from HAL in init()) */ private IEndpointCommunication mHubInterface = null; + /* + * The list of previous registration records. + */ + private static final int NUM_CLIENT_RECORDS = 20; + private final ConcurrentLinkedEvictingDeque<RegistrationRecord> mRegistrationRecordDeque = + new ConcurrentLinkedEvictingDeque<>(NUM_CLIENT_RECORDS); + + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = {"ACTION_"}, + value = { + ACTION_REGISTERED, + ACTION_UNREGISTERED, + }) + public @interface Action {} + + public static final int ACTION_REGISTERED = 0; + public static final int ACTION_UNREGISTERED = 1; + + /** A container class to store a record of ContextHubEndpointBroker registrations. */ + private class RegistrationRecord { + private final String mBroker; + private final int mAction; + private final long mTimestamp; + + RegistrationRecord(String broker, @Action int action) { + mBroker = broker; + mAction = action; + mTimestamp = System.currentTimeMillis(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(ContextHubServiceUtil.formatDateFromTimestamp(mTimestamp)); + sb.append(" "); + sb.append(mAction == ACTION_REGISTERED ? "+ " : "- "); + sb.append(mBroker); + return sb.toString(); + } + } + /* package */ ContextHubEndpointManager( Context context, IContextHubWrapper contextHubProxy, @@ -228,6 +274,7 @@ import java.util.function.Consumer; return null; } + mRegistrationRecordDeque.add(new RegistrationRecord(broker.toString(), ACTION_REGISTERED)); Log.d(TAG, "Registered endpoint with ID = " + endpointId); return IContextHubEndpoint.Stub.asInterface(broker); } @@ -274,7 +321,11 @@ import java.util.function.Consumer; * @param endpointId The ID of the endpoint to unregister. */ /* package */ void unregisterEndpoint(long endpointId) { - mEndpointMap.remove(endpointId); + ContextHubEndpointBroker broker = mEndpointMap.remove(endpointId); + if (broker != null) { + mRegistrationRecordDeque.add( + new RegistrationRecord(broker.toString(), ACTION_UNREGISTERED)); + } } /** Invoked by the service when the Context Hub HAL restarts. */ @@ -366,6 +417,27 @@ import java.util.function.Consumer; } } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + int count = 1; + for (ContextHubEndpointBroker broker : mEndpointMap.values()) { + sb.append(count + ". " + broker); + sb.append(System.lineSeparator()); + count++; + } + + sb.append(System.lineSeparator()); + sb.append("Registration History:"); + sb.append(System.lineSeparator()); + Iterator<RegistrationRecord> it = mRegistrationRecordDeque.descendingIterator(); + while (it.hasNext()) { + sb.append(it.next()); + sb.append(System.lineSeparator()); + } + return sb.toString(); + } + /** * Invokes a callback for a session with matching ID. * diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 502a7aeba258..bf7351cb11db 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -1579,6 +1579,12 @@ public class ContextHubService extends IContextHubService.Stub { pw.println("=================== CLIENTS ===================="); pw.println(mClientManager); + if (mEndpointManager != null) { + pw.println(""); + pw.println("=================== ENDPOINTS ===================="); + pw.println(mEndpointManager); + } + pw.println(""); pw.println("=================== TRANSACTIONS ===================="); pw.println(mTransactionManager); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS b/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS index bb487fb52c9f..ebf7e6bed064 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS @@ -1,4 +1,3 @@ aseemk@google.com bozhu@google.com dementyev@google.com -robertberry@google.com diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java index 42303e042561..b735b2447486 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java @@ -141,8 +141,7 @@ final class MediaRoute2ProviderWatcher { isSelfScanOnlyProvider |= MediaRoute2ProviderService.CATEGORY_SELF_SCAN_ONLY.equals(category); supportsSystemMediaRouting |= - MediaRoute2ProviderService.SERVICE_INTERFACE_SYSTEM_MEDIA.equals( - category); + MediaRoute2ProviderService.CATEGORY_SYSTEM_MEDIA.equals(category); } } int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 6c0d8ad7264d..debac9436bb3 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -1635,11 +1635,11 @@ class MediaRouter2ServiceImpl { manager)); } - List<MediaRoute2Info> routes = - userRecord.mHandler.mLastNotifiedRoutesToPrivilegedRouters.values().stream() - .toList(); userRecord.mHandler.sendMessage( - obtainMessage(ManagerRecord::notifyRoutesUpdated, managerRecord, routes)); + obtainMessage( + UserHandler::dispatchRoutesToManagerOnHandler, + userRecord.mHandler, + managerRecord)); } @GuardedBy("mLock") @@ -2119,6 +2119,9 @@ class MediaRouter2ServiceImpl { mHasBluetoothRoutingPermission.set(checkCallerHasBluetoothPermissions(mPid, mUid)); boolean newSystemRoutingPermissionValue = hasSystemRoutingPermission(); if (oldSystemRoutingPermissionValue != newSystemRoutingPermissionValue) { + // TODO: b/379788233 - Ensure access to fields like + // mLastNotifiedRoutesToPrivilegedRouters happens on the right thread. We might need + // to run this on the handler. Map<String, MediaRoute2Info> routesToReport = newSystemRoutingPermissionValue ? mUserRecord.mHandler.mLastNotifiedRoutesToPrivilegedRouters @@ -2543,6 +2546,8 @@ class MediaRouter2ServiceImpl { * both system route providers and user route providers. * * <p>See {@link #getRouterRecords(boolean hasModifyAudioRoutingPermission)}. + * + * <p>Must be accessed on this handler's thread. */ private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToPrivilegedRouters = new ArrayMap<>(); @@ -2558,6 +2563,8 @@ class MediaRouter2ServiceImpl { * (e.g. volume changes) to non-privileged routers. * * <p>See {@link SystemMediaRoute2Provider#mDefaultRoute}. + * + * <p>Must be accessed on this handler's thread. */ private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToNonPrivilegedRouters = new ArrayMap<>(); @@ -2800,7 +2807,7 @@ class MediaRouter2ServiceImpl { removedRoutes)); } - dispatchUpdates( + dispatchUpdatesOnHandler( hasAddedOrModifiedRoutes, hasRemovedRoutes, provider.mIsSystemRouteProvider, @@ -2822,6 +2829,13 @@ class MediaRouter2ServiceImpl { source, providerId, routesString); } + /** Notifies the given manager of the current routes. */ + public void dispatchRoutesToManagerOnHandler(ManagerRecord managerRecord) { + List<MediaRoute2Info> routes = + mLastNotifiedRoutesToPrivilegedRouters.values().stream().toList(); + managerRecord.notifyRoutesUpdated(routes); + } + /** * Dispatches the latest route updates in {@link #mLastNotifiedRoutesToPrivilegedRouters} * and {@link #mLastNotifiedRoutesToNonPrivilegedRouters} to registered {@link @@ -2834,7 +2848,7 @@ class MediaRouter2ServiceImpl { * @param isSystemProvider whether the latest update was caused by a system provider. * @param defaultRoute the current default route in {@link #mSystemProvider}. */ - private void dispatchUpdates( + private void dispatchUpdatesOnHandler( boolean hasAddedOrModifiedRoutes, boolean hasRemovedRoutes, boolean isSystemProvider, diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java index 0b8b115e65d0..91a2843ccaf7 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -120,6 +120,8 @@ public class MediaQualityService extends SystemService { private final Object mPictureProfileLock = new Object(); // A global lock for sound profile objects. private final Object mSoundProfileLock = new Object(); + // A global lock for user state objects. + private final Object mUserStateLock = new Object(); // A global lock for ambient backlight objects. private final Object mAmbientBacklightLock = new Object(); @@ -127,17 +129,17 @@ public class MediaQualityService extends SystemService { super(context); mContext = context; mHalAmbientBacklightCallback = new HalAmbientBacklightCallback(); - mPictureProfileAdjListener = new PictureProfileAdjustmentListenerImpl(mContext); - mSoundProfileAdjListener = new SoundProfileAdjustmentListenerImpl(mContext); mPackageManager = mContext.getPackageManager(); mPictureProfileTempIdMap = new BiMap<>(); mSoundProfileTempIdMap = new BiMap<>(); mMediaQualityDbHelper = new MediaQualityDbHelper(mContext); - mMqDatabaseUtils = new MqDatabaseUtils(mContext); mMediaQualityDbHelper.setWriteAheadLoggingEnabled(true); mMediaQualityDbHelper.setIdleConnectionTimeout(30); - mHalNotifier = new HalNotifier(); mMqManagerNotifier = new MqManagerNotifier(); + mMqDatabaseUtils = new MqDatabaseUtils(); + mHalNotifier = new HalNotifier(); + mPictureProfileAdjListener = new PictureProfileAdjustmentListenerImpl(); + mSoundProfileAdjListener = new SoundProfileAdjustmentListenerImpl(); // The package info in the context isn't initialized in the way it is for normal apps, // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we @@ -166,20 +168,21 @@ public class MediaQualityService extends SystemService { if (mMediaQuality != null) { try { mMediaQuality.setAmbientBacklightCallback(mHalAmbientBacklightCallback); + + mPpChangedListener = mMediaQuality.getPictureProfileListener(); + mSpChangedListener = mMediaQuality.getSoundProfileListener(); + mMediaQuality.setPictureProfileAdjustmentListener(mPictureProfileAdjListener); mMediaQuality.setSoundProfileAdjustmentListener(mSoundProfileAdjListener); + } catch (RemoteException e) { Slog.e(TAG, "Failed to set ambient backlight detector callback", e); } } - mPpChangedListener = IPictureProfileChangedListener.Stub.asInterface(binder); - mSpChangedListener = ISoundProfileChangedListener.Stub.asInterface(binder); - publishBinderService(Context.MEDIA_QUALITY_SERVICE, new BinderService()); } - // TODO: Add additional APIs. b/373951081 private final class BinderService extends IMediaQualityManager.Stub { @GuardedBy("mPictureProfileLock") @@ -225,7 +228,6 @@ public class MediaQualityService extends SystemService { PictureProfile.ERROR_NO_PERMISSION, Binder.getCallingUid(), Binder.getCallingPid()); } - synchronized (mPictureProfileLock) { ContentValues values = MediaQualityUtils.getContentValues(dbId, pp.getProfileType(), @@ -233,7 +235,6 @@ public class MediaQualityService extends SystemService { pp.getPackageName(), pp.getInputId(), pp.getParameters()); - updateDatabaseOnPictureProfileAndNotifyManagerAndHal(values, pp.getParameters()); } } @@ -269,12 +270,13 @@ public class MediaQualityService extends SystemService { mMqManagerNotifier.notifyOnPictureProfileError(id, PictureProfile.ERROR_INVALID_ARGUMENT, Binder.getCallingUid(), Binder.getCallingPid()); + } else { + mMqManagerNotifier.notifyOnPictureProfileRemoved( + mPictureProfileTempIdMap.getValue(dbId), toDelete, + Binder.getCallingUid(), Binder.getCallingPid()); + mPictureProfileTempIdMap.remove(dbId); + mHalNotifier.notifyHalOnPictureProfileChange(dbId, null); } - mMqManagerNotifier.notifyOnPictureProfileRemoved( - mPictureProfileTempIdMap.getValue(dbId), toDelete, - Binder.getCallingUid(), Binder.getCallingPid()); - mPictureProfileTempIdMap.remove(dbId); - mHalNotifier.notifyHalOnPictureProfileChange(dbId, null); } } } @@ -520,12 +522,13 @@ public class MediaQualityService extends SystemService { mMqManagerNotifier.notifyOnSoundProfileError(id, SoundProfile.ERROR_INVALID_ARGUMENT, Binder.getCallingUid(), Binder.getCallingPid()); + } else { + mMqManagerNotifier.notifyOnSoundProfileRemoved( + mSoundProfileTempIdMap.getValue(dbId), toDelete, + Binder.getCallingUid(), Binder.getCallingPid()); + mSoundProfileTempIdMap.remove(dbId); + mHalNotifier.notifyHalOnSoundProfileChange(dbId, null); } - mMqManagerNotifier.notifyOnSoundProfileRemoved( - mSoundProfileTempIdMap.getValue(dbId), toDelete, - Binder.getCallingUid(), Binder.getCallingPid()); - mSoundProfileTempIdMap.remove(dbId); - mHalNotifier.notifyHalOnSoundProfileChange(dbId, null); } } } @@ -684,24 +687,22 @@ public class MediaQualityService extends SystemService { mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED; } - //TODO: need lock here? @Override public void registerPictureProfileCallback(final IPictureProfileCallback callback) { int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); - UserState userState = getOrCreateUserStateLocked(Binder.getCallingUid()); + UserState userState = getOrCreateUserState(Binder.getCallingUid()); userState.mPictureProfileCallbackPidUidMap.put(callback, Pair.create(callingPid, callingUid)); } - //TODO: need lock here? @Override public void registerSoundProfileCallback(final ISoundProfileCallback callback) { int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); - UserState userState = getOrCreateUserStateLocked(Binder.getCallingUid()); + UserState userState = getOrCreateUserState(Binder.getCallingUid()); userState.mSoundProfileCallbackPidUidMap.put(callback, Pair.create(callingPid, callingUid)); } @@ -792,7 +793,6 @@ public class MediaQualityService extends SystemService { } } - //TODO: do I need a lock here? @Override public List<ParameterCapability> getParameterCapabilities( List<String> names, UserHandle user) { @@ -809,14 +809,20 @@ public class MediaQualityService extends SystemService { private List<ParameterCapability> getListParameterCapability(ParamCapability[] caps) { List<ParameterCapability> pcList = new ArrayList<>(); - for (ParamCapability pcHal : caps) { - String name = MediaQualityUtils.getParameterName(pcHal.name); - boolean isSupported = pcHal.isSupported; - int type = pcHal.defaultValue == null ? 0 : pcHal.defaultValue.getTag() + 1; - Bundle bundle = MediaQualityUtils.convertToCaps(type, pcHal.range); - pcList.add(new ParameterCapability(name, isSupported, type, bundle)); + if (caps != null) { + for (ParamCapability pcHal : caps) { + if (pcHal != null) { + String name = MediaQualityUtils.getParameterName(pcHal.name); + boolean isSupported = pcHal.isSupported; + int type = pcHal.defaultValue == null ? 0 : pcHal.defaultValue.getTag() + 1; + Bundle bundle = MediaQualityUtils.convertToCaps(type, pcHal.range); + + pcList.add(new ParameterCapability(name, isSupported, type, bundle)); + } + } } + return pcList; } @@ -1055,7 +1061,7 @@ public class MediaQualityService extends SystemService { synchronized (mPictureProfileLock) { for (int i = 0; i < mUserStates.size(); i++) { int userId = mUserStates.keyAt(i); - UserState userState = getOrCreateUserStateLocked(userId); + UserState userState = getOrCreateUserState(userId); userState.mPictureProfileCallbackPidUidMap.remove(callback); } } @@ -1069,7 +1075,7 @@ public class MediaQualityService extends SystemService { synchronized (mSoundProfileLock) { for (int i = 0; i < mUserStates.size(); i++) { int userId = mUserStates.keyAt(i); - UserState userState = getOrCreateUserStateLocked(userId); + UserState userState = getOrCreateUserState(userId); userState.mSoundProfileCallbackPidUidMap.remove(callback); } } @@ -1095,25 +1101,27 @@ public class MediaQualityService extends SystemService { } } - //TODO: used by both picture and sound. can i add both locks? - private UserState getOrCreateUserStateLocked(int userId) { - UserState userState = getUserStateLocked(userId); + @GuardedBy("mUserStateLock") + private UserState getOrCreateUserState(int userId) { + UserState userState = getUserState(userId); if (userState == null) { userState = new UserState(mContext, userId); - mUserStates.put(userId, userState); + synchronized (mUserStateLock) { + mUserStates.put(userId, userState); + } } return userState; } - //TODO: used by both picture and sound. can i add both locks? - private UserState getUserStateLocked(int userId) { - return mUserStates.get(userId); + @GuardedBy("mUserStateLock") + private UserState getUserState(int userId) { + synchronized (mUserStateLock) { + return mUserStates.get(userId); + } } private final class MqDatabaseUtils { - MediaQualityDbHelper mMediaQualityDbHelper; - private PictureProfile getPictureProfile(Long dbId) { String selection = BaseParameters.PARAMETER_ID + " = ?"; String[] selectionArguments = {Long.toString(dbId)}; @@ -1205,8 +1213,7 @@ public class MediaQualityService extends SystemService { /*groupBy=*/ null, /*having=*/ null, /*orderBy=*/ null); } - private MqDatabaseUtils(Context context) { - mMediaQualityDbHelper = new MediaQualityDbHelper(context); + private MqDatabaseUtils() { } } @@ -1264,7 +1271,7 @@ public class MediaQualityService extends SystemService { private void notifyPictureProfileHelper(int mode, String profileId, PictureProfile profile, Integer errorCode, List<ParameterCapability> paramCaps, int uid, int pid) { - UserState userState = getOrCreateUserStateLocked(UserHandle.USER_SYSTEM); + UserState userState = getOrCreateUserState(UserHandle.USER_SYSTEM); int n = userState.mPictureProfileCallbacks.beginBroadcast(); for (int i = 0; i < n; ++i) { @@ -1349,7 +1356,7 @@ public class MediaQualityService extends SystemService { private void notifySoundProfileHelper(int mode, String profileId, SoundProfile profile, Integer errorCode, List<ParameterCapability> paramCaps, int uid, int pid) { - UserState userState = getOrCreateUserStateLocked(UserHandle.USER_SYSTEM); + UserState userState = getOrCreateUserState(UserHandle.USER_SYSTEM); int n = userState.mSoundProfileCallbacks.beginBroadcast(); for (int i = 0; i < n; ++i) { @@ -1404,11 +1411,13 @@ public class MediaQualityService extends SystemService { private void notifyHalOnPictureProfileChange(Long dbId, PersistableBundle params) { // TODO: only notify HAL when the profile is active / being used - try { - mPpChangedListener.onPictureProfileChanged(convertToHalPictureProfile(dbId, - params)); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to notify HAL on picture profile change.", e); + if (mPpChangedListener != null) { + try { + mPpChangedListener.onPictureProfileChanged(convertToHalPictureProfile(dbId, + params)); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to notify HAL on picture profile change.", e); + } } } @@ -1429,10 +1438,13 @@ public class MediaQualityService extends SystemService { private void notifyHalOnSoundProfileChange(Long dbId, PersistableBundle params) { // TODO: only notify HAL when the profile is active / being used - try { - mSpChangedListener.onSoundProfileChanged(convertToHalSoundProfile(dbId, params)); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to notify HAL on sound profile change.", e); + if (mSpChangedListener != null) { + try { + mSpChangedListener + .onSoundProfileChanged(convertToHalSoundProfile(dbId, params)); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to notify HAL on sound profile change.", e); + } } } @@ -1488,9 +1500,6 @@ public class MediaQualityService extends SystemService { private final class PictureProfileAdjustmentListenerImpl extends IPictureProfileAdjustmentListener.Stub { - MqDatabaseUtils mMqDatabaseUtils; - MqManagerNotifier mMqManagerNotifier; - HalNotifier mHalNotifier; @Override public void onPictureProfileAdjusted( @@ -1542,18 +1551,13 @@ public class MediaQualityService extends SystemService { return null; } - private PictureProfileAdjustmentListenerImpl(Context context) { - mMqDatabaseUtils = new MqDatabaseUtils(context); - mMqManagerNotifier = new MqManagerNotifier(); - mHalNotifier = new HalNotifier(); + private PictureProfileAdjustmentListenerImpl() { + } } private final class SoundProfileAdjustmentListenerImpl extends ISoundProfileAdjustmentListener.Stub { - MqDatabaseUtils mMqDatabaseUtils; - MqManagerNotifier mMqManagerNotifier; - HalNotifier mHalNotifier; @Override public void onSoundProfileAdjusted( @@ -1598,10 +1602,8 @@ public class MediaQualityService extends SystemService { return null; } - private SoundProfileAdjustmentListenerImpl(Context context) { - mMqDatabaseUtils = new MqDatabaseUtils(context); - mMqManagerNotifier = new MqManagerNotifier(); - mHalNotifier = new HalNotifier(); + private SoundProfileAdjustmentListenerImpl() { + } } diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index b0ef80793cd7..62e26e189a35 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -25,6 +25,8 @@ import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND; +import static com.android.server.notification.Flags.FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER; +import static com.android.server.notification.Flags.managedServicesConcurrentMultiuser; import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled; import android.annotation.FlaggedApi; @@ -75,7 +77,9 @@ import com.android.internal.util.XmlUtils; import com.android.internal.util.function.TriPredicate; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.LocalServices; import com.android.server.notification.NotificationManagerService.DumpFilter; +import com.android.server.pm.UserManagerInternal; import com.android.server.utils.TimingsTraceAndSlog; import org.xmlpull.v1.XmlPullParser; @@ -134,6 +138,7 @@ abstract public class ManagedServices { private final UserProfiles mUserProfiles; protected final IPackageManager mPm; protected final UserManager mUm; + protected final UserManagerInternal mUmInternal; private final Config mConfig; private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -157,12 +162,17 @@ abstract public class ManagedServices { protected final ArraySet<String> mDefaultPackages = new ArraySet<>(); // lists the component names of all enabled (and therefore potentially connected) - // app services for current profiles. + // app services for each user. This is intended to support a concurrent multi-user environment. + // key value is the resolved userId. @GuardedBy("mMutex") - private final ArraySet<ComponentName> mEnabledServicesForCurrentProfiles = new ArraySet<>(); - // Just the packages from mEnabledServicesForCurrentProfiles + private final SparseArray<ArraySet<ComponentName>> mEnabledServicesByUser = + new SparseArray<>(); + // Just the packages from mEnabledServicesByUser + // This is intended to support a concurrent multi-user environment. + // key value is the resolved userId. @GuardedBy("mMutex") - private final ArraySet<String> mEnabledServicesPackageNames = new ArraySet<>(); + private final SparseArray<ArraySet<String>> mEnabledServicesPackageNamesByUser = + new SparseArray<>(); // Per user id, list of enabled packages that have nevertheless asked not to be run @GuardedBy("mSnoozing") private final SparseSetArray<ComponentName> mSnoozing = new SparseSetArray<>(); @@ -195,6 +205,10 @@ abstract public class ManagedServices { mConfig = getConfig(); mApprovalLevel = APPROVAL_BY_COMPONENT; mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mUmInternal = LocalServices.getService(UserManagerInternal.class); + // Initialize for the current user. + mEnabledServicesByUser.put(UserHandle.USER_CURRENT, new ArraySet<>()); + mEnabledServicesPackageNamesByUser.put(UserHandle.USER_CURRENT, new ArraySet<>()); } abstract protected Config getConfig(); @@ -383,11 +397,30 @@ abstract public class ManagedServices { } synchronized (mMutex) { - pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size() - + ") enabled for current profiles:"); - for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) { - if (filter != null && !filter.matches(cmpt)) continue; - pw.println(" " + cmpt); + if (managedServicesConcurrentMultiuser()) { + for (int i = 0; i < mEnabledServicesByUser.size(); i++) { + final int userId = mEnabledServicesByUser.keyAt(i); + final ArraySet<ComponentName> componentNames = + mEnabledServicesByUser.get(userId); + String userString = userId == UserHandle.USER_CURRENT + ? "current profiles" : "user " + Integer.toString(userId); + pw.println(" All " + getCaption() + "s (" + componentNames.size() + + ") enabled for " + userString + ":"); + for (ComponentName cmpt : componentNames) { + if (filter != null && !filter.matches(cmpt)) continue; + pw.println(" " + cmpt); + } + } + } else { + final ArraySet<ComponentName> enabledServicesForCurrentProfiles = + mEnabledServicesByUser.get(UserHandle.USER_CURRENT); + pw.println(" All " + getCaption() + "s (" + + enabledServicesForCurrentProfiles.size() + + ") enabled for current profiles:"); + for (ComponentName cmpt : enabledServicesForCurrentProfiles) { + if (filter != null && !filter.matches(cmpt)) continue; + pw.println(" " + cmpt); + } } pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):"); @@ -442,11 +475,24 @@ abstract public class ManagedServices { } } - synchronized (mMutex) { - for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) { - if (filter != null && !filter.matches(cmpt)) continue; - cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED); + if (managedServicesConcurrentMultiuser()) { + for (int i = 0; i < mEnabledServicesByUser.size(); i++) { + final int userId = mEnabledServicesByUser.keyAt(i); + final ArraySet<ComponentName> componentNames = + mEnabledServicesByUser.get(userId); + for (ComponentName cmpt : componentNames) { + if (filter != null && !filter.matches(cmpt)) continue; + cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED); + } + } + } else { + final ArraySet<ComponentName> enabledServicesForCurrentProfiles = + mEnabledServicesByUser.get(UserHandle.USER_CURRENT); + for (ComponentName cmpt : enabledServicesForCurrentProfiles) { + if (filter != null && !filter.matches(cmpt)) continue; + cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED); + } } for (ManagedServiceInfo info : mServices) { if (filter != null && !filter.matches(info.component)) continue; @@ -841,9 +887,31 @@ abstract public class ManagedServices { } } + /** convenience method for looking in mEnabledServicesPackageNamesByUser + * for UserHandle.USER_CURRENT. + * This is a legacy API. When FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER becomes + * trunk stable, this API should be deprecated. Additionally, when this method + * is deprecated, the unit tests written using this method should also be revised. + * + * @param pkg target package name + * @return boolean value that indicates whether it is enabled for the current profiles + */ protected boolean isComponentEnabledForPackage(String pkg) { + return isComponentEnabledForPackage(pkg, UserHandle.USER_CURRENT); + } + + /** convenience method for looking in mEnabledServicesPackageNamesByUser + * + * @param pkg target package name + * @param userId the id of the target user + * @return boolean value that indicates whether it is enabled for the target user + */ + @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + protected boolean isComponentEnabledForPackage(String pkg, int userId) { synchronized (mMutex) { - return mEnabledServicesPackageNames.contains(pkg); + ArraySet<String> enabledServicesPackageNames = + mEnabledServicesPackageNamesByUser.get(resolveUserId(userId)); + return enabledServicesPackageNames != null && enabledServicesPackageNames.contains(pkg); } } @@ -1016,9 +1084,14 @@ abstract public class ManagedServices { public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) { if (DEBUG) { synchronized (mMutex) { + int resolvedUserId = (managedServicesConcurrentMultiuser() + && (uidList != null && uidList.length > 0)) + ? resolveUserId(UserHandle.getUserId(uidList[0])) + : UserHandle.USER_CURRENT; Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList)) - + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames); + + " mEnabledServicesPackageNames=" + + mEnabledServicesPackageNamesByUser.get(resolvedUserId)); } } @@ -1034,11 +1107,18 @@ abstract public class ManagedServices { } } for (String pkgName : pkgList) { - if (isComponentEnabledForPackage(pkgName)) { - anyServicesInvolved = true; + if (!managedServicesConcurrentMultiuser()) { + if (isComponentEnabledForPackage(pkgName)) { + anyServicesInvolved = true; + } } if (uidList != null && uidList.length > 0) { for (int uid : uidList) { + if (managedServicesConcurrentMultiuser()) { + if (isComponentEnabledForPackage(pkgName, UserHandle.getUserId(uid))) { + anyServicesInvolved = true; + } + } if (isPackageAllowed(pkgName, UserHandle.getUserId(uid))) { anyServicesInvolved = true; trimApprovedListsForInvalidServices(pkgName, UserHandle.getUserId(uid)); @@ -1065,6 +1145,36 @@ abstract public class ManagedServices { unbindUserServices(user); } + /** + * Call this method when a user is stopped + * + * @param user the id of the stopped user + */ + public void onUserStopped(int user) { + if (!managedServicesConcurrentMultiuser()) { + return; + } + boolean hasAny = false; + synchronized (mMutex) { + if (mEnabledServicesByUser.contains(user) + && mEnabledServicesPackageNamesByUser.contains(user)) { + // Through the ManagedServices.resolveUserId, + // we resolve UserHandle.USER_CURRENT as the key for users + // other than the visible background user. + // Therefore, the user IDs that exist as keys for each member variable + // correspond to the visible background user. + // We need to unbind services of the stopped visible background user. + mEnabledServicesByUser.remove(user); + mEnabledServicesPackageNamesByUser.remove(user); + hasAny = true; + } + } + if (hasAny) { + Slog.i(TAG, "Removing approved services for stopped user " + user); + unbindUserServices(user); + } + } + public void onUserSwitched(int user) { if (DEBUG) Slog.d(TAG, "onUserSwitched u=" + user); unbindOtherUserServices(user); @@ -1386,19 +1496,42 @@ abstract public class ManagedServices { protected void populateComponentsToBind(SparseArray<Set<ComponentName>> componentsToBind, final IntArray activeUsers, SparseArray<ArraySet<ComponentName>> approvedComponentsByUser) { - mEnabledServicesForCurrentProfiles.clear(); - mEnabledServicesPackageNames.clear(); final int nUserIds = activeUsers.size(); - + if (managedServicesConcurrentMultiuser()) { + for (int i = 0; i < nUserIds; ++i) { + final int resolvedUserId = resolveUserId(activeUsers.get(i)); + if (mEnabledServicesByUser.get(resolvedUserId) != null) { + mEnabledServicesByUser.get(resolvedUserId).clear(); + } + if (mEnabledServicesPackageNamesByUser.get(resolvedUserId) != null) { + mEnabledServicesPackageNamesByUser.get(resolvedUserId).clear(); + } + } + } else { + mEnabledServicesByUser.get(UserHandle.USER_CURRENT).clear(); + mEnabledServicesPackageNamesByUser.get(UserHandle.USER_CURRENT).clear(); + } for (int i = 0; i < nUserIds; ++i) { - // decode the list of components final int userId = activeUsers.get(i); + // decode the list of components final ArraySet<ComponentName> userComponents = approvedComponentsByUser.get(userId); if (null == userComponents) { componentsToBind.put(userId, new ArraySet<>()); continue; } + final int resolvedUserId = managedServicesConcurrentMultiuser() + ? resolveUserId(userId) + : UserHandle.USER_CURRENT; + ArraySet<ComponentName> enabledServices = + mEnabledServicesByUser.contains(resolvedUserId) + ? mEnabledServicesByUser.get(resolvedUserId) + : new ArraySet<>(); + ArraySet<String> enabledServicesPackageName = + mEnabledServicesPackageNamesByUser.contains(resolvedUserId) + ? mEnabledServicesPackageNamesByUser.get(resolvedUserId) + : new ArraySet<>(); + final Set<ComponentName> add = new HashSet<>(userComponents); synchronized (mSnoozing) { ArraySet<ComponentName> snoozed = mSnoozing.get(userId); @@ -1409,12 +1542,12 @@ abstract public class ManagedServices { componentsToBind.put(userId, add); - mEnabledServicesForCurrentProfiles.addAll(userComponents); - + enabledServices.addAll(userComponents); for (int j = 0; j < userComponents.size(); j++) { - final ComponentName component = userComponents.valueAt(j); - mEnabledServicesPackageNames.add(component.getPackageName()); + enabledServicesPackageName.add(userComponents.valueAt(j).getPackageName()); } + mEnabledServicesByUser.put(resolvedUserId, enabledServices); + mEnabledServicesPackageNamesByUser.put(resolvedUserId, enabledServicesPackageName); } } @@ -1453,13 +1586,9 @@ abstract public class ManagedServices { */ protected void rebindServices(boolean forceRebind, int userToRebind) { if (DEBUG) Slog.d(TAG, "rebindServices " + forceRebind + " " + userToRebind); - IntArray userIds = mUserProfiles.getCurrentProfileIds(); boolean rebindAllCurrentUsers = mUserProfiles.isProfileUser(userToRebind, mContext) && allowRebindForParentUser(); - if (userToRebind != USER_ALL && !rebindAllCurrentUsers) { - userIds = new IntArray(1); - userIds.add(userToRebind); - } + IntArray userIds = getUserIdsForRebindServices(userToRebind, rebindAllCurrentUsers); final SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>(); final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>(); @@ -1483,6 +1612,23 @@ abstract public class ManagedServices { bindToServices(componentsToBind); } + private IntArray getUserIdsForRebindServices(int userToRebind, boolean rebindAllCurrentUsers) { + IntArray userIds = mUserProfiles.getCurrentProfileIds(); + if (userToRebind != USER_ALL && !rebindAllCurrentUsers) { + userIds = new IntArray(1); + userIds.add(userToRebind); + } else if (managedServicesConcurrentMultiuser() + && userToRebind == USER_ALL) { + for (UserInfo user : mUm.getUsers()) { + if (mUmInternal.isVisibleBackgroundFullUser(user.id) + && !userIds.contains(user.id)) { + userIds.add(user.id); + } + } + } + return userIds; + } + /** * Called when user switched to unbind all services from other users. */ @@ -1506,7 +1652,11 @@ abstract public class ManagedServices { synchronized (mMutex) { final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices(); for (ManagedServiceInfo info : removableBoundServices) { - if ((allExceptUser && (info.userid != user)) + // User switching is the event for the forground user. + // It should not affect the service of the visible background user. + if ((allExceptUser && (info.userid != user) + && !(managedServicesConcurrentMultiuser() + && info.isVisibleBackgroundUserService)) || (!allExceptUser && (info.userid == user))) { Set<ComponentName> toUnbind = componentsToUnbind.get(info.userid, new ArraySet<>()); @@ -1861,6 +2011,29 @@ abstract public class ManagedServices { } /** + * This method returns the mapped id for the incoming user id + * If the incoming id was not the id of the visible background user, it returns USER_CURRENT. + * In the other cases, it returns the same value as the input. + * + * @param userId the id of the user + * @return the user id if it is a visible background user, otherwise + * {@link UserHandle#USER_CURRENT} + */ + @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + @VisibleForTesting + public int resolveUserId(int userId) { + if (managedServicesConcurrentMultiuser()) { + if (mUmInternal.isVisibleBackgroundFullUser(userId)) { + // The dataset of the visible background user should be managed independently. + return userId; + } + } + // The data of current user and its profile users need to be managed + // in a dataset as before. + return UserHandle.USER_CURRENT; + } + + /** * Returns true if services in the parent user should be rebound * when rebindServices is called with a profile userId. * Must be false for NotificationAssistants. @@ -1878,6 +2051,8 @@ abstract public class ManagedServices { public int targetSdkVersion; public Pair<ComponentName, Integer> mKey; public int uid; + @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public boolean isVisibleBackgroundUserService; public ManagedServiceInfo(IInterface service, ComponentName component, int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion, @@ -1889,6 +2064,10 @@ abstract public class ManagedServices { this.connection = connection; this.targetSdkVersion = targetSdkVersion; this.uid = uid; + if (managedServicesConcurrentMultiuser()) { + this.isVisibleBackgroundUserService = LocalServices + .getService(UserManagerInternal.class).isVisibleBackgroundFullUser(userid); + } mKey = Pair.create(component, userid); } @@ -1937,19 +2116,28 @@ abstract public class ManagedServices { } public boolean isSameUser(int userId) { - if (!isEnabledForCurrentProfiles()) { + if (!isEnabledForUser()) { return false; } return userId == USER_ALL || userId == this.userid; } public boolean enabledAndUserMatches(int nid) { - if (!isEnabledForCurrentProfiles()) { + if (!isEnabledForUser()) { return false; } if (this.userid == USER_ALL) return true; if (this.isSystem) return true; if (nid == USER_ALL || nid == this.userid) return true; + if (managedServicesConcurrentMultiuser() + && mUmInternal.getProfileParentId(nid) + != mUmInternal.getProfileParentId(this.userid)) { + // If the profile parent IDs do not match each other, + // it is determined that the users do not match. + // This situation may occur when comparing the current user's ID + // with the visible background user's ID. + return false; + } return supportsProfiles() && mUserProfiles.isCurrentProfile(nid) && isPermittedForProfile(nid); @@ -1969,12 +2157,21 @@ abstract public class ManagedServices { removeServiceImpl(this.service, this.userid); } - /** convenience method for looking in mEnabledServicesForCurrentProfiles */ - public boolean isEnabledForCurrentProfiles() { + /** + * convenience method for looking in mEnabledServicesByUser. + * If FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER is disabled, this manages the data using + * only UserHandle.USER_CURRENT as the key, in order to behave the same as the legacy logic. + */ + public boolean isEnabledForUser() { if (this.isSystem) return true; if (this.connection == null) return false; synchronized (mMutex) { - return mEnabledServicesForCurrentProfiles.contains(this.component); + int resolvedUserId = managedServicesConcurrentMultiuser() + ? resolveUserId(this.userid) + : UserHandle.USER_CURRENT; + ArraySet<ComponentName> enabledServices = + mEnabledServicesByUser.get(resolvedUserId); + return enabledServices != null && enabledServices.contains(this.component); } } @@ -2017,10 +2214,30 @@ abstract public class ManagedServices { } } - /** convenience method for looking in mEnabledServicesForCurrentProfiles */ + /** convenience method for looking in mEnabledServicesByUser for UserHandle.USER_CURRENT. + * This is a legacy API. When FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER becomes + * trunk stable, this API should be deprecated. Additionally, when this method + * is deprecated, the unit tests written using this method should also be revised. + * + * @param component target component name + * @return boolean value that indicates whether it is enabled for the current profiles + */ public boolean isComponentEnabledForCurrentProfiles(ComponentName component) { + return isComponentEnabledForUser(component, UserHandle.USER_CURRENT); + } + + /** convenience method for looking in mEnabledServicesForUser + * + * @param component target component name + * @param userId the id of the target user + * @return boolean value that indicates whether it is enabled for the target user + */ + @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public boolean isComponentEnabledForUser(ComponentName component, int userId) { synchronized (mMutex) { - return mEnabledServicesForCurrentProfiles.contains(component); + ArraySet<ComponentName> enabledServicesForUser = + mEnabledServicesByUser.get(resolveUserId(userId)); + return enabledServicesForUser != null && enabledServicesForUser.contains(component); } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 340afb776405..6fddfb5f90a7 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -173,6 +173,7 @@ import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER; import static com.android.server.notification.Flags.expireBitmaps; +import static com.android.server.notification.Flags.managedServicesConcurrentMultiuser; import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER; import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT; import static com.android.server.utils.PriorityDump.PRIORITY_ARG; @@ -1207,7 +1208,7 @@ public class NotificationManagerService extends SystemService { } mAssistants.resetDefaultAssistantsIfNecessary(); - mPreferencesHelper.syncChannelsBypassingDnd(); + mPreferencesHelper.syncHasPriorityChannels(); } @VisibleForTesting @@ -2323,6 +2324,9 @@ public class NotificationManagerService extends SystemService { if (userHandle >= 0) { cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle, REASON_USER_STOPPED); + mConditionProviders.onUserStopped(userHandle); + mListeners.onUserStopped(userHandle); + mAssistants.onUserStopped(userHandle); } } else if ( isProfileUnavailable(action)) { @@ -2343,7 +2347,7 @@ public class NotificationManagerService extends SystemService { mConditionProviders.onUserSwitched(userId); mListeners.onUserSwitched(userId); mZenModeHelper.onUserSwitched(userId); - mPreferencesHelper.syncChannelsBypassingDnd(); + mPreferencesHelper.syncHasPriorityChannels(); } // assistant is the only thing that cares about managed profiles specifically mAssistants.onUserSwitched(userId); @@ -2367,7 +2371,7 @@ public class NotificationManagerService extends SystemService { mConditionProviders.onUserRemoved(userId); mAssistants.onUserRemoved(userId); mHistoryManager.onUserRemoved(userId); - mPreferencesHelper.syncChannelsBypassingDnd(); + mPreferencesHelper.syncHasPriorityChannels(); handleSavePolicyFile(); } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); @@ -2376,9 +2380,6 @@ public class NotificationManagerService extends SystemService { if (!mUserProfiles.isProfileUser(userId, context)) { mConditionProviders.onUserUnlocked(userId); mListeners.onUserUnlocked(userId); - if (!android.app.Flags.modesApi()) { - mZenModeHelper.onUserUnlocked(userId); - } } } } @@ -2767,9 +2768,7 @@ public class NotificationManagerService extends SystemService { void onPolicyChanged(Policy newPolicy) { Binder.withCleanCallingIdentity(() -> { Intent intent = new Intent(ACTION_NOTIFICATION_POLICY_CHANGED); - if (android.app.Flags.modesApi()) { - intent.putExtra(EXTRA_NOTIFICATION_POLICY, newPolicy); - } + intent.putExtra(EXTRA_NOTIFICATION_POLICY, newPolicy); sendRegisteredOnlyBroadcast(intent); mRankingHandler.requestSort(); }); @@ -2778,11 +2777,10 @@ public class NotificationManagerService extends SystemService { @Override void onConsolidatedPolicyChanged(Policy newConsolidatedPolicy) { Binder.withCleanCallingIdentity(() -> { - if (android.app.Flags.modesApi()) { - Intent intent = new Intent(ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED); - intent.putExtra(EXTRA_NOTIFICATION_POLICY, newConsolidatedPolicy); - sendRegisteredOnlyBroadcast(intent); - } + Intent intent = new Intent(ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED); + intent.putExtra(EXTRA_NOTIFICATION_POLICY, newConsolidatedPolicy); + sendRegisteredOnlyBroadcast(intent); + mRankingHandler.requestSort(); }); } @@ -3368,7 +3366,7 @@ public class NotificationManagerService extends SystemService { migrateDefaultNAS(); maybeShowInitialReviewPermissionsNotification(); - if (android.app.Flags.modesApi() && !mZenModeHelper.hasDeviceEffectsApplier()) { + if (!mZenModeHelper.hasDeviceEffectsApplier()) { // Cannot be done earlier, as some services aren't ready until this point. mZenModeHelper.setDeviceEffectsApplier( new DefaultDeviceEffectsApplier(getContext())); @@ -3446,7 +3444,7 @@ public class NotificationManagerService extends SystemService { mConditionProviders.onUserSwitched(userId); mListeners.onUserSwitched(userId); mZenModeHelper.onUserSwitched(userId); - mPreferencesHelper.syncChannelsBypassingDnd(); + mPreferencesHelper.syncHasPriorityChannels(); } // assistant is the only thing that cares about managed profiles specifically mAssistants.onUserSwitched(userId); @@ -5236,11 +5234,8 @@ public class NotificationManagerService extends SystemService { @Override public boolean areChannelsBypassingDnd() { - if (android.app.Flags.modesApi()) { - return mZenModeHelper.getConsolidatedNotificationPolicy().allowPriorityChannels() - && mPreferencesHelper.areChannelsBypassingDnd(); - } - return mPreferencesHelper.areChannelsBypassingDnd(); + return mZenModeHelper.getConsolidatedNotificationPolicy().allowPriorityChannels() + && mPreferencesHelper.hasPriorityChannels(); } @Override @@ -5730,12 +5725,13 @@ public class NotificationManagerService extends SystemService { public void requestBindListener(ComponentName component) { checkCallerIsSystemOrSameApp(component.getPackageName()); int uid = Binder.getCallingUid(); + int userId = UserHandle.getUserId(uid); final long identity = Binder.clearCallingIdentity(); try { - ManagedServices manager = - mAssistants.isComponentEnabledForCurrentProfiles(component) - ? mAssistants - : mListeners; + boolean isAssistantEnabled = managedServicesConcurrentMultiuser() + ? mAssistants.isComponentEnabledForUser(component, userId) + : mAssistants.isComponentEnabledForCurrentProfiles(component); + ManagedServices manager = isAssistantEnabled ? mAssistants : mListeners; manager.setComponentState(component, UserHandle.getUserId(uid), true); } finally { Binder.restoreCallingIdentity(identity); @@ -5762,16 +5758,16 @@ public class NotificationManagerService extends SystemService { public void requestUnbindListenerComponent(ComponentName component) { checkCallerIsSameApp(component.getPackageName()); int uid = Binder.getCallingUid(); + int userId = UserHandle.getUserId(uid); final long identity = Binder.clearCallingIdentity(); try { synchronized (mNotificationLock) { - ManagedServices manager = - mAssistants.isComponentEnabledForCurrentProfiles(component) - ? mAssistants - : mListeners; - if (manager.isPackageOrComponentAllowed(component.flattenToString(), - UserHandle.getUserId(uid))) { - manager.setComponentState(component, UserHandle.getUserId(uid), false); + boolean isAssistantEnabled = managedServicesConcurrentMultiuser() + ? mAssistants.isComponentEnabledForUser(component, userId) + : mAssistants.isComponentEnabledForCurrentProfiles(component); + ManagedServices manager = isAssistantEnabled ? mAssistants : mListeners; + if (manager.isPackageOrComponentAllowed(component.flattenToString(), userId)) { + manager.setComponentState(component, userId, false); } } } finally { @@ -6092,43 +6088,27 @@ public class NotificationManagerService extends SystemService { @Override public void requestInterruptionFilterFromListener(INotificationListener token, int interruptionFilter) throws RemoteException { - if (android.app.Flags.modesApi()) { - final int callingUid = Binder.getCallingUid(); - ManagedServiceInfo info; - synchronized (mNotificationLock) { - info = mListeners.checkServiceTokenLocked(token); - } + final int callingUid = Binder.getCallingUid(); + ManagedServiceInfo info; + synchronized (mNotificationLock) { + info = mListeners.checkServiceTokenLocked(token); + } - final int zenMode = zenModeFromInterruptionFilter(interruptionFilter, -1); - if (zenMode == -1) return; + final int zenMode = zenModeFromInterruptionFilter(interruptionFilter, -1); + if (zenMode == -1) return; - UserHandle zenUser = getCallingZenUser(); - if (!canManageGlobalZenPolicy(info.component.getPackageName(), callingUid)) { - mZenModeHelper.applyGlobalZenModeAsImplicitZenRule( - zenUser, info.component.getPackageName(), callingUid, zenMode); - } else { - int origin = computeZenOrigin(/* fromUser= */ false); - Binder.withCleanCallingIdentity(() -> { - mZenModeHelper.setManualZenMode(zenUser, zenMode, /* conditionId= */ null, - origin, "listener:" + info.component.flattenToShortString(), - /* caller= */ info.component.getPackageName(), - callingUid); - }); - } + UserHandle zenUser = getCallingZenUser(); + if (!canManageGlobalZenPolicy(info.component.getPackageName(), callingUid)) { + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule( + zenUser, info.component.getPackageName(), callingUid, zenMode); } else { - final int callingUid = Binder.getCallingUid(); - final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi(); - final long identity = Binder.clearCallingIdentity(); - try { - synchronized (mNotificationLock) { - final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); - mZenModeHelper.requestFromListener(info.component, interruptionFilter, - callingUid, isSystemOrSystemUi); - updateInterruptionFilterLocked(); - } - } finally { - Binder.restoreCallingIdentity(identity); - } + int origin = computeZenOrigin(/* fromUser= */ false); + Binder.withCleanCallingIdentity(() -> { + mZenModeHelper.setManualZenMode(zenUser, zenMode, /* conditionId= */ null, + origin, "listener:" + info.component.flattenToShortString(), + /* caller= */ info.component.getPackageName(), + callingUid); + }); } } @@ -6177,19 +6157,8 @@ public class NotificationManagerService extends SystemService { } } - // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined. - @Override - public List<ZenModeConfig.ZenRule> getZenRules() throws RemoteException { - int callingUid = Binder.getCallingUid(); - enforcePolicyAccess(callingUid, "getZenRules"); - return mZenModeHelper.getZenRules(getCallingZenUser(), callingUid); - } - @Override public Map<String, AutomaticZenRule> getAutomaticZenRules() { - if (!android.app.Flags.modesApi()) { - throw new IllegalStateException("getAutomaticZenRules called with flag off!"); - } int callingUid = Binder.getCallingUid(); enforcePolicyAccess(callingUid, "getAutomaticZenRules"); return mZenModeHelper.getAutomaticZenRules(getCallingZenUser(), callingUid); @@ -6260,50 +6229,40 @@ public class NotificationManagerService extends SystemService { // Implicit rules have no ConditionProvider or Activity. We allow the user to customize // them (via Settings), but not the owner app. Should the app want to start using it as // a "normal" rule, it must provide a CP/ConfigActivity too. - if (android.app.Flags.modesApi()) { - boolean isImplicitRuleUpdateFromSystem = updateId != null - && ZenModeConfig.isImplicitRuleId(updateId) - && isCallerSystemOrSystemUi(); - if (!isImplicitRuleUpdateFromSystem - && rule.getOwner() == null - && rule.getConfigurationActivity() == null) { - throw new NullPointerException( - "Rule must have a ConditionProviderService and/or configuration " - + "activity"); - } - } else { - if (rule.getOwner() == null && rule.getConfigurationActivity() == null) { - throw new NullPointerException( - "Rule must have a ConditionProviderService and/or configuration " - + "activity"); - } + boolean isImplicitRuleUpdateFromSystem = updateId != null + && ZenModeConfig.isImplicitRuleId(updateId) + && isCallerSystemOrSystemUi(); + if (!isImplicitRuleUpdateFromSystem + && rule.getOwner() == null + && rule.getConfigurationActivity() == null) { + throw new NullPointerException( + "Rule must have a ConditionProviderService and/or configuration " + + "activity"); } Objects.requireNonNull(rule.getConditionId(), "ConditionId is null"); - if (android.app.Flags.modesApi()) { - if (isCallerSystemOrSystemUi()) { - return; // System callers can use any type. - } - int uid = Binder.getCallingUid(); - int userId = UserHandle.getUserId(uid); + if (isCallerSystemOrSystemUi()) { + return; // System callers can use any type. + } + int uid = Binder.getCallingUid(); + int userId = UserHandle.getUserId(uid); - if (rule.getType() == AutomaticZenRule.TYPE_MANAGED) { - boolean isDeviceOwner = Binder.withCleanCallingIdentity( - () -> mDpm.isActiveDeviceOwner(uid)); - if (!isDeviceOwner) { - throw new IllegalArgumentException( - "Only Device Owners can use AutomaticZenRules with TYPE_MANAGED"); - } - } else if (rule.getType() == AutomaticZenRule.TYPE_BEDTIME) { - String wellbeingPackage = getContext().getResources().getString( - com.android.internal.R.string.config_systemWellbeing); - boolean isCallerWellbeing = !TextUtils.isEmpty(wellbeingPackage) - && mPackageManagerInternal.isSameApp(wellbeingPackage, uid, userId); - if (!isCallerWellbeing) { - throw new IllegalArgumentException( - "Only the 'Wellbeing' package can use AutomaticZenRules with " - + "TYPE_BEDTIME"); - } + if (rule.getType() == AutomaticZenRule.TYPE_MANAGED) { + boolean isDeviceOwner = Binder.withCleanCallingIdentity( + () -> mDpm.isActiveDeviceOwner(uid)); + if (!isDeviceOwner) { + throw new IllegalArgumentException( + "Only Device Owners can use AutomaticZenRules with TYPE_MANAGED"); + } + } else if (rule.getType() == AutomaticZenRule.TYPE_BEDTIME) { + String wellbeingPackage = getContext().getResources().getString( + com.android.internal.R.string.config_systemWellbeing); + boolean isCallerWellbeing = !TextUtils.isEmpty(wellbeingPackage) + && mPackageManagerInternal.isSameApp(wellbeingPackage, uid, userId); + if (!isCallerWellbeing) { + throw new IllegalArgumentException( + "Only the 'Wellbeing' package can use AutomaticZenRules with " + + "TYPE_BEDTIME"); } } } @@ -6386,9 +6345,7 @@ public class NotificationManagerService extends SystemService { @ZenModeConfig.ConfigOrigin private int computeZenOrigin(boolean fromUser) { - // "fromUser" is introduced with MODES_API, so only consider it in that case. - // (Non-MODES_API behavior should also not depend at all on ORIGIN_USER_IN_X). - if (android.app.Flags.modesApi() && fromUser) { + if (fromUser) { if (isCallerSystemOrSystemUi()) { return ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI; } else { @@ -6402,9 +6359,7 @@ public class NotificationManagerService extends SystemService { } private void enforceUserOriginOnlyFromSystem(boolean fromUser, String method) { - if (android.app.Flags.modesApi() - && fromUser - && !isCallerSystemOrSystemUiOrShell()) { + if (fromUser && !isCallerSystemOrSystemUiOrShell()) { throw new SecurityException(TextUtils.formatSimple( "Calling %s with fromUser == true is only allowed for system", method)); } @@ -6419,7 +6374,7 @@ public class NotificationManagerService extends SystemService { enforceUserOriginOnlyFromSystem(fromUser, "setInterruptionFilter"); UserHandle zenUser = getCallingZenUser(); - if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) { + if (!canManageGlobalZenPolicy(pkg, callingUid)) { mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(zenUser, pkg, callingUid, zen); return; } @@ -6549,6 +6504,13 @@ public class NotificationManagerService extends SystemService { } catch (NameNotFoundException e) { return false; } + if (managedServicesConcurrentMultiuser()) { + return checkPackagePolicyAccess(pkg) + || mListeners.isComponentEnabledForPackage(pkg, + UserHandle.getCallingUserId()) + || (mDpm != null + && (mDpm.isActiveProfileOwner(uid) || mDpm.isActiveDeviceOwner(uid))); + } //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode. return checkPackagePolicyAccess(pkg) || mListeners.isComponentEnabledForPackage(pkg) @@ -6723,7 +6685,7 @@ public class NotificationManagerService extends SystemService { public Policy getNotificationPolicy(String pkg) { final int callingUid = Binder.getCallingUid(); UserHandle zenUser = getCallingZenUser(); - if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) { + if (!canManageGlobalZenPolicy(pkg, callingUid)) { return mZenModeHelper.getNotificationPolicyFromImplicitZenRule(zenUser, pkg); } final long identity = Binder.clearCallingIdentity(); @@ -6760,8 +6722,7 @@ public class NotificationManagerService extends SystemService { UserHandle zenUser = getCallingZenUser(); boolean isSystemCaller = isCallerSystemOrSystemUiOrShell(); - boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi() - && !canManageGlobalZenPolicy(pkg, callingUid); + boolean shouldApplyAsImplicitRule = !canManageGlobalZenPolicy(pkg, callingUid); final long identity = Binder.clearCallingIdentity(); try { @@ -6953,7 +6914,8 @@ public class NotificationManagerService extends SystemService { android.Manifest.permission.INTERACT_ACROSS_USERS, "setNotificationListenerAccessGrantedForUser for user " + userId); } - if (mUmInternal.isVisibleBackgroundFullUser(userId)) { + if (!managedServicesConcurrentMultiuser() + && mUmInternal.isVisibleBackgroundFullUser(userId)) { // The main use case for visible background users is the Automotive multi-display // configuration where a passenger can use a secondary display while the driver is // using the main display. NotificationListeners is designed only for the current @@ -8219,9 +8181,6 @@ public class NotificationManagerService extends SystemService { @Override public void setDeviceEffectsApplier(DeviceEffectsApplier applier) { - if (!android.app.Flags.modesApi()) { - return; - } if (mZenModeHelper == null) { throw new IllegalStateException("ZenModeHelper is not yet ready!"); } @@ -13165,7 +13124,8 @@ public class NotificationManagerService extends SystemService { @Override public void onUserUnlocked(int user) { - if (mUmInternal.isVisibleBackgroundFullUser(user)) { + if (!managedServicesConcurrentMultiuser() + && mUmInternal.isVisibleBackgroundFullUser(user)) { // The main use case for visible background users is the Automotive // multi-display configuration where a passenger can use a secondary // display while the driver is using the main display. @@ -13805,7 +13765,7 @@ public class NotificationManagerService extends SystemService { // TODO (b/73052211): if the ranking update changed the notification type, // cancel notifications for NLSes that can't see them anymore for (final ManagedServiceInfo serviceInfo : getServices()) { - if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener( + if (!serviceInfo.isEnabledForUser() || !isInteractionVisibleToListener( serviceInfo, ActivityManager.getCurrentUser())) { continue; } @@ -13833,7 +13793,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") public void notifyListenerHintsChangedLocked(final int hints) { for (final ManagedServiceInfo serviceInfo : getServices()) { - if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener( + if (!serviceInfo.isEnabledForUser() || !isInteractionVisibleToListener( serviceInfo, ActivityManager.getCurrentUser())) { continue; } @@ -13889,7 +13849,7 @@ public class NotificationManagerService extends SystemService { public void notifyInterruptionFilterChanged(final int interruptionFilter) { for (final ManagedServiceInfo serviceInfo : getServices()) { - if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener( + if (!serviceInfo.isEnabledForUser() || !isInteractionVisibleToListener( serviceInfo, ActivityManager.getCurrentUser())) { continue; } diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java index 6c0035b82a86..f680ce722e81 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java @@ -514,11 +514,14 @@ interface NotificationRecordLogger { final int fsi_state; final boolean is_locked; final int age_in_minutes; + final boolean is_promoted_ongoing; + final boolean has_promotable_characteristics; @DurationMillisLong long post_duration_millis; // Not final; calculated at the end. NotificationReported(NotificationRecordPair p, NotificationReportedEvent eventType, int position, int buzzBeepBlink, InstanceId groupId) { + final Notification notification = p.r.getSbn().getNotification(); this.event_id = eventType.getId(); this.uid = p.r.getUid(); this.package_name = p.r.getSbn().getPackageName(); @@ -527,8 +530,8 @@ interface NotificationRecordLogger { this.channel_id_hash = p.getChannelIdHash(); this.group_id_hash = p.getGroupIdHash(); this.group_instance_id = (groupId == null) ? 0 : groupId.getId(); - this.is_group_summary = p.r.getSbn().getNotification().isGroupSummary(); - this.category = p.r.getSbn().getNotification().category; + this.is_group_summary = notification.isGroupSummary(); + this.category = notification.category; this.style = p.getStyle(); this.num_people = p.getNumPeople(); this.position = position; @@ -542,22 +545,18 @@ interface NotificationRecordLogger { this.assistant_ranking_score = p.r.getRankingScore(); this.is_ongoing = p.r.getSbn().isOngoing(); this.is_foreground_service = NotificationRecordLogger.isForegroundService(p.r); - this.timeout_millis = p.r.getSbn().getNotification().getTimeoutAfter(); + this.timeout_millis = notification.getTimeoutAfter(); this.is_non_dismissible = NotificationRecordLogger.isNonDismissible(p.r); - - final boolean hasFullScreenIntent = - p.r.getSbn().getNotification().fullScreenIntent != null; - - final boolean hasFsiRequestedButDeniedFlag = (p.r.getSbn().getNotification().flags - & Notification.FLAG_FSI_REQUESTED_BUT_DENIED) != 0; - + final boolean hasFullScreenIntent = notification.fullScreenIntent != null; + final boolean hasFsiRequestedButDeniedFlag = + (notification.flags & Notification.FLAG_FSI_REQUESTED_BUT_DENIED) != 0; this.fsi_state = NotificationRecordLogger.getFsiState( hasFullScreenIntent, hasFsiRequestedButDeniedFlag, eventType); - this.is_locked = p.r.isLocked(); - this.age_in_minutes = NotificationRecordLogger.getAgeInMinutes( - p.r.getSbn().getPostTime(), p.r.getSbn().getNotification().getWhen()); + p.r.getSbn().getPostTime(), notification.getWhen()); + this.is_promoted_ongoing = notification.isPromotedOngoing(); + this.has_promotable_characteristics = notification.hasPromotableCharacteristics(); } } diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java index fc0a7764963e..e0e3fbaf49f1 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java @@ -78,7 +78,9 @@ class NotificationRecordLoggerImpl implements NotificationRecordLogger { notificationReported.post_duration_millis, notificationReported.fsi_state, notificationReported.is_locked, - notificationReported.age_in_minutes); + notificationReported.age_in_minutes, + notificationReported.is_promoted_ongoing, + notificationReported.has_promotable_characteristics); } @Override diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java index c305d66c24c1..bc987ed21251 100644 --- a/services/core/java/com/android/server/notification/NotificationShellCmd.java +++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java @@ -183,13 +183,8 @@ public class NotificationShellCmd extends ShellCommand { interruptionFilter = INTERRUPTION_FILTER_ALL; } final int filter = interruptionFilter; - if (android.app.Flags.modesApi()) { - mBinderService.setInterruptionFilter(callingPackage, filter, - /* fromUser= */ true); - } else { - mBinderService.setInterruptionFilter(callingPackage, filter, - /* fromUser= */ false); - } + mBinderService.setInterruptionFilter(callingPackage, filter, + /* fromUser= */ true); } break; case "allow_dnd": { diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 3974c839fd38..fff812c038e7 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -42,6 +42,7 @@ import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_P import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED; +import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE; import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_PROMOTABLE; import android.annotation.FlaggedApi; @@ -233,11 +234,9 @@ public class PreferencesHelper implements RankingConfig { private SparseBooleanArray mLockScreenShowNotifications; private SparseBooleanArray mLockScreenPrivateNotifications; private boolean mIsMediaNotificationFilteringEnabled; - // When modes_api flag is enabled, this value only tracks whether the current user has any - // channels marked as "priority channels", but not necessarily whether they are permitted - // to bypass DND by current zen policy. - // TODO: b/310620812 - Rename to be more accurate when modes_api flag is inlined. - private boolean mCurrentUserHasChannelsBypassingDnd; + // Whether the current user has any channels marked as "priority channels" -- but not + // necessarily whether they are permitted to bypass DND by current zen policy. + private boolean mCurrentUserHasPriorityChannels; private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS; private final boolean mShowReviewPermissionsNotification; @@ -288,7 +287,7 @@ public class PreferencesHelper implements RankingConfig { if (!TAG_RANKING.equals(tag)) return; final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1); - boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE; + boolean upgradeForBubbles = xmlVersion >= XML_VERSION_BUBBLES_UPGRADE; boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION); if (mShowReviewPermissionsNotification && (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION)) { @@ -339,15 +338,19 @@ public class PreferencesHelper implements RankingConfig { } boolean skipWarningLogged = false; boolean skipGroupWarningLogged = false; - boolean hasSAWPermission = false; - if (upgradeForBubbles && uid != UNKNOWN_UID) { - hasSAWPermission = mAppOps.noteOpNoThrow( - OP_SYSTEM_ALERT_WINDOW, uid, name, null, - "check-notif-bubble") == AppOpsManager.MODE_ALLOWED; - } - int bubblePref = hasSAWPermission - ? BUBBLE_PREFERENCE_ALL - : parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE); + int bubblePref = parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, + DEFAULT_BUBBLE_PREFERENCE); + boolean bubbleLocked = (parser.getAttributeInt(null, + ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS) & USER_LOCKED_BUBBLE) + != 0; + if (!bubbleLocked + && upgradeForBubbles + && uid != UNKNOWN_UID + && mAppOps.noteOpNoThrow(OP_SYSTEM_ALERT_WINDOW, uid, name, null, + "check-notif-bubble") == AppOpsManager.MODE_ALLOWED) { + // User hasn't changed bubble pref & the app has SAW, so allow all bubbles. + bubblePref = BUBBLE_PREFERENCE_ALL; + } int appImportance = parser.getAttributeInt(null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE); // when data is loaded from disk it's loaded as USER_ALL, but restored data that @@ -1063,7 +1066,7 @@ public class PreferencesHelper implements RankingConfig { r.groups.put(group.getId(), group); } if (needsDndChange) { - updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi); + updateCurrentUserHasPriorityChannels(callingUid, fromSystemOrSystemUi); } if (android.app.Flags.nmBinderPerfCacheChannels() && changed) { invalidateNotificationChannelGroupCache(); @@ -1150,7 +1153,7 @@ public class PreferencesHelper implements RankingConfig { existing.setBypassDnd(bypassDnd); needsPolicyFileChange = true; - if (bypassDnd != mCurrentUserHasChannelsBypassingDnd + if (bypassDnd != mCurrentUserHasPriorityChannels || previousExistingImportance != existing.getImportance()) { needsDndChange = true; } @@ -1214,7 +1217,7 @@ public class PreferencesHelper implements RankingConfig { } r.channels.put(channel.getId(), channel); - if (channel.canBypassDnd() != mCurrentUserHasChannelsBypassingDnd) { + if (channel.canBypassDnd() != mCurrentUserHasPriorityChannels) { needsDndChange = true; } MetricsLogger.action(getChannelLog(channel, pkg).setType( @@ -1224,7 +1227,7 @@ public class PreferencesHelper implements RankingConfig { } if (needsDndChange) { - updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi); + updateCurrentUserHasPriorityChannels(callingUid, fromSystemOrSystemUi); } if (android.app.Flags.nmBinderPerfCacheChannels() && needsPolicyFileChange) { @@ -1317,14 +1320,14 @@ public class PreferencesHelper implements RankingConfig { // relevantly affected without the parent channel already having been. } - if (updatedChannel.canBypassDnd() != mCurrentUserHasChannelsBypassingDnd + if (updatedChannel.canBypassDnd() != mCurrentUserHasPriorityChannels || channel.getImportance() != updatedChannel.getImportance()) { needsDndChange = true; changed = true; } } if (needsDndChange) { - updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi); + updateCurrentUserHasPriorityChannels(callingUid, fromSystemOrSystemUi); } if (changed) { if (android.app.Flags.nmBinderPerfCacheChannels()) { @@ -1550,7 +1553,7 @@ public class PreferencesHelper implements RankingConfig { } } if (channelBypassedDnd) { - updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi); + updateCurrentUserHasPriorityChannels(callingUid, fromSystemOrSystemUi); } if (android.app.Flags.nmBinderPerfCacheChannels() && deletedChannel) { @@ -1745,7 +1748,7 @@ public class PreferencesHelper implements RankingConfig { } } if (groupBypassedDnd) { - updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi); + updateCurrentUserHasPriorityChannels(callingUid, fromSystemOrSystemUi); } if (android.app.Flags.nmBinderPerfCacheChannels()) { if (deletedChannels.size() > 0) { @@ -1906,8 +1909,8 @@ public class PreferencesHelper implements RankingConfig { } } if (!deletedChannelIds.isEmpty()) { - if (mCurrentUserHasChannelsBypassingDnd) { - updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi); + if (mCurrentUserHasPriorityChannels) { + updateCurrentUserHasPriorityChannels(callingUid, fromSystemOrSystemUi); } if (android.app.Flags.nmBinderPerfCacheChannels()) { invalidateNotificationChannelCache(); @@ -2098,7 +2101,7 @@ public class PreferencesHelper implements RankingConfig { } /** - * Syncs {@link #mCurrentUserHasChannelsBypassingDnd} with the current user's notification + * Syncs {@link #mCurrentUserHasPriorityChannels} with the current user's notification * policy before updating. Must be called: * <ul> * <li>On system init, after channels and DND configurations are loaded. @@ -2106,22 +2109,23 @@ public class PreferencesHelper implements RankingConfig { * <li>If users are removed (the removed user could've been a profile of the current one). * </ul> */ - void syncChannelsBypassingDnd() { - mCurrentUserHasChannelsBypassingDnd = + void syncHasPriorityChannels() { + mCurrentUserHasPriorityChannels = (mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).state - & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) != 0; + & NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS) != 0; - updateCurrentUserHasChannelsBypassingDnd(/* callingUid= */ Process.SYSTEM_UID, + updateCurrentUserHasPriorityChannels(/* callingUid= */ Process.SYSTEM_UID, /* fromSystemOrSystemUi= */ true); } /** * Updates the user's NotificationPolicy based on whether the current userId has channels - * bypassing DND. It should be called whenever a channel is created, updated, or deleted, or - * when the current user (or its profiles) change. + * marked as "priority" (which might bypass DND, depending on the zen rule details). It should + * be called whenever a channel is created, updated, or deleted, or when the current user (or + * its profiles) change. */ // TODO: b/368247671 - remove fromSystemOrSystemUi argument when modes_ui is inlined. - private void updateCurrentUserHasChannelsBypassingDnd(int callingUid, + private void updateCurrentUserHasPriorityChannels(int callingUid, boolean fromSystemOrSystemUi) { ArraySet<Pair<String, Integer>> candidatePkgs = new ArraySet<>(); @@ -2149,13 +2153,13 @@ public class PreferencesHelper implements RankingConfig { } } boolean haveBypassingApps = candidatePkgs.size() > 0; - if (mCurrentUserHasChannelsBypassingDnd != haveBypassingApps) { - mCurrentUserHasChannelsBypassingDnd = haveBypassingApps; + if (mCurrentUserHasPriorityChannels != haveBypassingApps) { + mCurrentUserHasPriorityChannels = haveBypassingApps; if (android.app.Flags.modesUi()) { mZenModeHelper.updateHasPriorityChannels(UserHandle.CURRENT, - mCurrentUserHasChannelsBypassingDnd); + mCurrentUserHasPriorityChannels); } else { - updateZenPolicy(mCurrentUserHasChannelsBypassingDnd, callingUid, + updateZenPolicy(mCurrentUserHasPriorityChannels, callingUid, fromSystemOrSystemUi); } } @@ -2188,16 +2192,20 @@ public class PreferencesHelper implements RankingConfig { policy.priorityCategories, policy.priorityCallSenders, policy.priorityMessageSenders, policy.suppressedVisualEffects, (areChannelsBypassingDnd - ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND : 0), + ? NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS : 0), policy.priorityConversationSenders), fromSystemOrSystemUi ? ZenModeConfig.ORIGIN_SYSTEM : ZenModeConfig.ORIGIN_APP, callingUid); } - // TODO: b/310620812 - rename to hasPriorityChannels() when modes_api is inlined. - public boolean areChannelsBypassingDnd() { - return mCurrentUserHasChannelsBypassingDnd; + /** + * Whether the current user has any channels marked as "priority channels" + * ({@link NotificationChannel#canBypassDnd}), but not necessarily whether they are permitted + * to bypass the filters set by the current zen policy. + */ + public boolean hasPriorityChannels() { + return mCurrentUserHasPriorityChannels; } /** diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java index fcc5e9771f94..ec9a2db52de9 100644 --- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java +++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java @@ -16,7 +16,7 @@ package com.android.server.notification; -import static android.app.NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND; +import static android.app.NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS; import static android.provider.Settings.Global.ZEN_MODE_OFF; import static android.service.notification.NotificationServiceProto.CHANNEL_POLICY_NONE; import static android.service.notification.NotificationServiceProto.CHANNEL_POLICY_PRIORITY; @@ -285,11 +285,10 @@ class ZenModeEventLogger { return true; } - if (Flags.modesApi() && hasActiveRuleCountDiff()) { - // Rules with INTERRUPTION_FILTER_ALL were always possible but before MODES_API - // they were completely useless; now they can apply effects, so we want to log - // when they become active/inactive, even though DND itself (as in "notification - // blocking") is off. + if (hasActiveRuleCountDiff()) { + // Rules with INTERRUPTION_FILTER_ALL can apply effects, so we want to log when they + // become active/inactive, even though DND itself (as in "notification blocking") + // is off. return true; } @@ -331,7 +330,7 @@ class ZenModeEventLogger { } } - if (Flags.modesApi() && mNewZenMode == ZEN_MODE_OFF) { + if (mNewZenMode == ZEN_MODE_OFF) { // If the mode is OFF -> OFF then there cannot be any *effective* change to policy. // (Note that, in theory, a policy diff is impossible since we don't merge the // policies of INTERRUPTION_FILTER_ALL rules; this is a "just in case" check). @@ -439,24 +438,14 @@ class ZenModeEventLogger { // Determine the number of (automatic & manual) rules active after the change takes place. int getNumRulesActive() { - if (!Flags.modesApi()) { - // If the zen mode has turned off, that means nothing can be active. - if (mNewZenMode == ZEN_MODE_OFF) { - return 0; - } - } return numActiveRulesInConfig(mNewConfig); } /** - * Return a list of the types of each of the active rules in the configuration. - * Only available when {@code MODES_API} is active; otherwise returns an empty list. + * Return a list of the types of each of the active rules in the configuration (sorted by + * the numerical value of the type, and including duplicates). */ int[] getActiveRuleTypes() { - if (!Flags.modesApi()) { - return new int[0]; - } - ArrayList<Integer> activeTypes = new ArrayList<>(); List<ZenRule> activeRules = activeRulesList(mNewConfig); if (activeRules.size() == 0) { @@ -476,77 +465,10 @@ class ZenModeEventLogger { return out; } - /** - * Return our best guess as to whether the changes observed are due to a user action. - * Note that this (before {@code MODES_API}) won't be 100% accurate as we can't necessarily - * distinguish between a system uid call indicating "user interacted with Settings" vs "a - * system app changed something automatically". - */ + /** Return whether the changes observed are due to a user action. */ boolean getIsUserAction() { - if (Flags.modesApi()) { - return mOrigin == ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI - || mOrigin == ZenModeConfig.ORIGIN_USER_IN_APP; - } - - // Approach for pre-MODES_API: - // - if manual rule turned on or off, the calling UID is system, and the new manual - // rule does not have an enabler set, guess that this is likely to be a user action. - // This may represent a system app turning on DND automatically, but we guess "user" - // in this case. - // - note that this has a known failure mode of "manual rule turning off - // automatically after the default time runs out". We currently have no way - // of distinguishing this case from a user manually turning off the rule. - // - the reason for checking the enabler field is that a call may look like it's - // coming from a system UID, but if an enabler is set then the request came - // from an external source. "enabler" will be blank when manual rule is turned - // on from Quick Settings or Settings. - // - if an automatic rule's state changes in whether it is "enabled", then - // that is probably a user action. - // - if an automatic rule goes from "not snoozing" to "snoozing", that is probably - // a user action; that means that the user temporarily turned off DND associated - // with that rule. - // - if an automatic rule becomes active but does *not* change in its enabled state - // (covered by a previous case anyway), we guess that this is an automatic change. - // - if a rule is added or removed and the call comes from the system, we guess that - // this is a user action (as system rules can't be added or removed without a user - // action). - switch (getChangedRuleType()) { - case RULE_TYPE_MANUAL: - // TODO(b/278888961): Distinguish the automatically-turned-off state - return isFromSystemOrSystemUi() && (getNewManualRuleEnabler() == null); - case RULE_TYPE_AUTOMATIC: - for (ZenModeDiff.RuleDiff d : getChangedAutomaticRules().values()) { - if (d.wasAdded() || d.wasRemoved()) { - // If the change comes from system, a rule being added/removed indicates - // a likely user action. From an app, it's harder to know for sure. - return isFromSystemOrSystemUi(); - } - ZenModeDiff.FieldDiff enabled = d.getDiffForField( - ZenModeDiff.RuleDiff.FIELD_ENABLED); - if (enabled != null && enabled.hasDiff()) { - return true; - } - ZenModeDiff.FieldDiff snoozing = d.getDiffForField( - ZenModeDiff.RuleDiff.FIELD_SNOOZING); - if (snoozing != null && snoozing.hasDiff() && (boolean) snoozing.to()) { - return true; - } - } - // If the change was in an automatic rule and none of the "probably triggered - // by a user" cases apply, then it's probably an automatic change. - return false; - case RULE_TYPE_UNKNOWN: - default: - } - - // If the change wasn't in a rule, but was in the zen policy: consider to be user action - // if the calling uid is system - if (hasPolicyDiff() || hasChannelsBypassingDiff()) { - return mCallingUid == Process.SYSTEM_UID; - } - - // don't know, or none of the other things triggered; assume not a user action - return false; + return mOrigin == ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI + || mOrigin == ZenModeConfig.ORIGIN_USER_IN_APP; } boolean isFromSystemOrSystemUi() { @@ -587,7 +509,7 @@ class ZenModeEventLogger { */ @Nullable byte[] getDNDPolicyProto() { - if (Flags.modesApi() && mNewZenMode == ZEN_MODE_OFF) { + if (mNewZenMode == ZEN_MODE_OFF) { return null; } @@ -628,13 +550,10 @@ class ZenModeEventLogger { mNewPolicy.allowMessagesFrom())); proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM, mNewPolicy.allowConversationsFrom()); - - if (Flags.modesApi()) { - proto.write(DNDPolicyProto.ALLOW_CHANNELS, - mNewPolicy.allowPriorityChannels() - ? CHANNEL_POLICY_PRIORITY - : CHANNEL_POLICY_NONE); - } + proto.write(DNDPolicyProto.ALLOW_CHANNELS, + mNewPolicy.allowPriorityChannels() + ? CHANNEL_POLICY_PRIORITY + : CHANNEL_POLICY_NONE); } else { Log.wtf(TAG, "attempted to write zen mode log event with null policy"); } @@ -648,14 +567,14 @@ class ZenModeEventLogger { */ boolean getAreChannelsBypassing() { if (mNewPolicy != null) { - return (mNewPolicy.state & STATE_CHANNELS_BYPASSING_DND) != 0; + return (mNewPolicy.state & STATE_HAS_PRIORITY_CHANNELS) != 0; } return false; } private boolean hasChannelsBypassingDiff() { boolean prevChannelsBypassing = mPrevPolicy != null - ? (mPrevPolicy.state & STATE_CHANNELS_BYPASSING_DND) != 0 : false; + ? (mPrevPolicy.state & STATE_HAS_PRIORITY_CHANNELS) != 0 : false; return prevChannelsBypassing != getAreChannelsBypassing(); } diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java index bdca555707e3..87ae78195ff5 100644 --- a/services/core/java/com/android/server/notification/ZenModeFiltering.java +++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java @@ -19,7 +19,6 @@ package com.android.server.notification; import static android.provider.Settings.Global.ZEN_MODE_OFF; import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE; -import android.app.Flags; import android.app.Notification; import android.app.NotificationManager; import android.content.ComponentName; @@ -146,16 +145,12 @@ public class ZenModeFiltering { // Returns whether the record is permitted to bypass DND when the zen mode is // ZEN_MODE_IMPORTANT_INTERRUPTIONS. This depends on whether the record's package priority is - // marked as PRIORITY_MAX (an indication of it belonging to a priority channel), and, if - // the modes_api flag is on, whether the given policy permits priority channels to bypass. - // TODO: b/310620812 - simplify when modes_api is inlined. + // marked as PRIORITY_MAX (an indication of it belonging to a priority channel), and whether the + // given policy permits priority channels to bypass. private boolean canRecordBypassDnd(NotificationRecord record, NotificationManager.Policy policy) { boolean inPriorityChannel = record.getPackagePriority() == Notification.PRIORITY_MAX; - if (Flags.modesApi()) { - return inPriorityChannel && policy.allowPriorityChannels(); - } - return inPriorityChannel; + return inPriorityChannel && policy.allowPriorityChannels(); } /** diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index b39b6fde6258..889df512dd60 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -157,6 +157,12 @@ public class ZenModeHelper { static final int RULE_LIMIT_PER_PACKAGE = 100; private static final Duration DELETED_RULE_KEPT_FOR = Duration.ofDays(30); + /** + * Amount of time since last activation after which implicit rules that have never been + * customized by the user are automatically cleaned up. + */ + private static final Duration IMPLICIT_RULE_KEPT_FOR = Duration.ofDays(30); + private static final int MAX_ICON_RESOURCE_NAME_LENGTH = 1000; /** @@ -326,9 +332,6 @@ public class ZenModeHelper { * applied immediately. */ void setDeviceEffectsApplier(@NonNull DeviceEffectsApplier deviceEffectsApplier) { - if (!Flags.modesApi()) { - return; - } synchronized (mConfigLock) { if (mDeviceEffectsApplier != null) { throw new IllegalStateException("Already set up a DeviceEffectsApplier!"); @@ -350,11 +353,6 @@ public class ZenModeHelper { } } - // TODO: b/310620812 - Remove when MODES_API is inlined (no more callers). - public void onUserUnlocked(int user) { - loadConfigForUser(user, "onUserUnlocked"); - } - void setPriorityOnlyDndExemptPackages(String[] packages) { mPriorityOnlyDndExemptPackages = packages; } @@ -385,21 +383,6 @@ public class ZenModeHelper { return NotificationManager.zenModeToInterruptionFilter(mZenMode); } - // TODO: b/310620812 - Remove when MODES_API is inlined (no more callers). - public void requestFromListener(ComponentName name, int filter, int callingUid, - boolean fromSystemOrSystemUi) { - final int newZen = NotificationManager.zenModeFromInterruptionFilter(filter, -1); - if (newZen != -1) { - // This change is known to be for UserHandle.CURRENT because NLSes for - // background users are unbound. - setManualZenMode(UserHandle.CURRENT, newZen, null, - fromSystemOrSystemUi ? ORIGIN_SYSTEM : ORIGIN_APP, - /* reason= */ "listener:" + (name != null ? name.flattenToShortString() : null), - /* caller= */ name != null ? name.getPackageName() : null, - callingUid); - } - } - public void setSuppressedEffects(long suppressedEffects) { if (mSuppressedEffects == suppressedEffects) return; mSuppressedEffects = suppressedEffects; @@ -414,33 +397,24 @@ public class ZenModeHelper { return mZenMode; } - // TODO: b/310620812 - Make private (or inline) when MODES_API is inlined. - public List<ZenRule> getZenRules(UserHandle user, int callingUid) { - List<ZenRule> rules = new ArrayList<>(); + /** + * Get the list of {@link AutomaticZenRule} instances that the calling package can manage + * (which means the owned rules for a regular app, and every rule for system callers) together + * with their ids. + */ + Map<String, AutomaticZenRule> getAutomaticZenRules(UserHandle user, int callingUid) { + HashMap<String, AutomaticZenRule> rules = new HashMap<>(); synchronized (mConfigLock) { ZenModeConfig config = getConfigLocked(user); if (config == null) return rules; + for (ZenRule rule : config.automaticRules.values()) { if (canManageAutomaticZenRule(rule, callingUid)) { - rules.add(rule); + rules.put(rule.id, zenRuleToAutomaticZenRule(rule)); } } + return rules; } - return rules; - } - - /** - * Get the list of {@link AutomaticZenRule} instances that the calling package can manage - * (which means the owned rules for a regular app, and every rule for system callers) together - * with their ids. - */ - Map<String, AutomaticZenRule> getAutomaticZenRules(UserHandle user, int callingUid) { - List<ZenRule> ruleList = getZenRules(user, callingUid); - HashMap<String, AutomaticZenRule> rules = new HashMap<>(ruleList.size()); - for (ZenRule rule : ruleList) { - rules.put(rule.id, zenRuleToAutomaticZenRule(rule)); - } - return rules; } public AutomaticZenRule getAutomaticZenRule(UserHandle user, String id, int callingUid) { @@ -511,9 +485,6 @@ public class ZenModeHelper { @GuardedBy("mConfigLock") private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, String pkg, ZenRule ruleToAdd, AutomaticZenRule azrToAdd, @ConfigOrigin int origin) { - if (!Flags.modesApi()) { - return ruleToAdd; - } String deletedKey = ZenModeConfig.deletedRuleKey(ruleToAdd); if (deletedKey == null) { // Couldn't calculate the deletedRuleKey (condition or pkg null?). This should @@ -561,9 +532,6 @@ public class ZenModeHelper { */ private static void maybeReplaceDefaultRule(ZenModeConfig config, @Nullable ZenRule oldRule, AutomaticZenRule rule) { - if (!Flags.modesApi()) { - return; - } if (rule.getType() == AutomaticZenRule.TYPE_BEDTIME && (oldRule == null || oldRule.type != rule.getType())) { // Note: we must not verify canManageAutomaticZenRule here, since most likely they @@ -572,7 +540,7 @@ public class ZenModeHelper { ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID); if (sleepingRule != null && !sleepingRule.enabled - && sleepingRule.canBeUpdatedByApp() /* meaning it's not user-customized */) { + && !sleepingRule.isUserModified()) { config.automaticRules.remove(ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID); } } @@ -599,18 +567,10 @@ public class ZenModeHelper { } ZenModeConfig newConfig = config.copy(); ZenModeConfig.ZenRule newRule = requireNonNull(newConfig.automaticRules.get(ruleId)); - if (!Flags.modesApi()) { - if (newRule.enabled != automaticZenRule.isEnabled()) { - dispatchOnAutomaticRuleStatusChanged(config.user, newRule.getPkg(), ruleId, - automaticZenRule.isEnabled() - ? AUTOMATIC_RULE_STATUS_ENABLED - : AUTOMATIC_RULE_STATUS_DISABLED); - } - } boolean updated = populateZenRule(newRule.pkg, automaticZenRule, newConfig, newRule, origin, /* isNew= */ false); - if (Flags.modesApi() && !updated) { + if (!updated) { // Bail out so we don't have the side effects of updating a rule (i.e. dropping // condition) when no changes happen. return true; @@ -643,10 +603,6 @@ public class ZenModeHelper { */ void applyGlobalZenModeAsImplicitZenRule(UserHandle user, String callingPkg, int callingUid, int zenMode) { - if (!android.app.Flags.modesApi()) { - Log.wtf(TAG, "applyGlobalZenModeAsImplicitZenRule called with flag off!"); - return; - } synchronized (mConfigLock) { ZenModeConfig config = getConfigLocked(user); if (config == null) { @@ -712,10 +668,6 @@ public class ZenModeHelper { */ void applyGlobalPolicyAsImplicitZenRule(UserHandle user, String callingPkg, int callingUid, NotificationManager.Policy policy) { - if (!android.app.Flags.modesApi()) { - Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!"); - return; - } synchronized (mConfigLock) { ZenModeConfig config = getConfigLocked(user); if (config == null) { @@ -772,10 +724,6 @@ public class ZenModeHelper { */ @Nullable Policy getNotificationPolicyFromImplicitZenRule(UserHandle user, String callingPkg) { - if (!android.app.Flags.modesApi()) { - Log.wtf(TAG, "getNotificationPolicyFromImplicitZenRule called with flag off!"); - return getNotificationPolicy(user); - } synchronized (mConfigLock) { ZenModeConfig config = getConfigLocked(user); if (config == null) { @@ -814,7 +762,6 @@ public class ZenModeHelper { .appendPath(pkg) .build(); rule.enabled = true; - rule.modified = false; rule.component = null; rule.configurationActivity = null; return rule; @@ -918,15 +865,12 @@ public class ZenModeHelper { private void maybePreserveRemovedRule(ZenModeConfig config, ZenRule ruleToRemove, @ConfigOrigin int origin) { - if (!Flags.modesApi()) { - return; - } // If an app deletes a previously customized rule, keep it around to preserve // the user's customization when/if it's recreated later. // We don't try to preserve system-owned rules because their conditionIds (used as // deletedRuleKey) are not stable. This is almost moot anyway because an app cannot // delete a system-owned rule. - if (origin == ORIGIN_APP && !ruleToRemove.canBeUpdatedByApp() + if (origin == ORIGIN_APP && ruleToRemove.isUserModified() && !PACKAGE_ANDROID.equals(ruleToRemove.pkg)) { String deletedKey = ZenModeConfig.deletedRuleKey(ruleToRemove); if (deletedKey != null) { @@ -952,7 +896,7 @@ public class ZenModeHelper { if (rule == null || !canManageAutomaticZenRule(rule, callingUid)) { return Condition.STATE_UNKNOWN; } - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { return rule.isActive() ? STATE_TRUE : STATE_FALSE; } else { // Buggy, does not consider snoozing! @@ -971,16 +915,9 @@ public class ZenModeHelper { newConfig = config.copy(); ZenRule rule = newConfig.automaticRules.get(id); - if (Flags.modesApi()) { - if (rule != null && canManageAutomaticZenRule(rule, callingUid)) { - setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule), - condition, origin, "setAzrState: " + rule.id, callingUid); - } - } else { - ArrayList<ZenRule> rules = new ArrayList<>(); - rules.add(rule); // rule may be null and throw NPE in the next method. - setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin, - "setAzrState: " + (rule != null ? rule.id : "null!"), callingUid); + if (rule != null && canManageAutomaticZenRule(rule, callingUid)) { + setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule), + condition, origin, "setAzrState: " + rule.id, callingUid); } } } @@ -995,13 +932,12 @@ public class ZenModeHelper { newConfig = config.copy(); List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleConditionId, condition); - if (Flags.modesApi()) { - for (int i = matchingRules.size() - 1; i >= 0; i--) { - if (!canManageAutomaticZenRule(matchingRules.get(i), callingUid)) { - matchingRules.remove(i); - } + for (int i = matchingRules.size() - 1; i >= 0; i--) { + if (!canManageAutomaticZenRule(matchingRules.get(i), callingUid)) { + matchingRules.remove(i); } } + setAutomaticZenRuleStateLocked(newConfig, matchingRules, condition, origin, "setAzrStateFromCps: " + ruleConditionId, callingUid); } @@ -1013,7 +949,7 @@ public class ZenModeHelper { if (rules == null || rules.isEmpty()) return; if (!Flags.modesUi()) { - if (Flags.modesApi() && condition.source == SOURCE_USER_ACTION) { + if (condition.source == SOURCE_USER_ACTION) { origin = ORIGIN_USER_IN_APP; // Although coming from app, it's actually from user. } } @@ -1026,7 +962,7 @@ public class ZenModeHelper { private static void applyConditionAndReconsiderOverride(ZenRule rule, Condition condition, int origin) { - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { if (isImplicitRuleId(rule.id)) { // Implicit rules do not use overrides, and always apply conditions directly. // This is compatible with the previous behavior (where the package set the @@ -1173,8 +1109,7 @@ public class ZenModeHelper { // if default rule wasn't user-modified use localized name // instead of previous system name if (currRule != null - && !currRule.modified - && (currRule.zenPolicyUserModifiedFields & AutomaticZenRule.FIELD_NAME) == 0 + && (currRule.userModifiedFields & AutomaticZenRule.FIELD_NAME) == 0 && !defaultRule.name.equals(currRule.name)) { if (DEBUG) { Slog.d(TAG, "Locale change - updating default zen rule name " @@ -1184,7 +1119,7 @@ public class ZenModeHelper { updated = true; } } - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { for (ZenRule rule : newConfig.automaticRules.values()) { if (SystemZenRules.isSystemOwnedRule(rule)) { updated |= SystemZenRules.updateTriggerDescription(mContext, rule); @@ -1256,172 +1191,145 @@ public class ZenModeHelper { @GuardedBy("mConfigLock") private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenModeConfig config, ZenRule rule, @ConfigOrigin int origin, boolean isNew) { - if (Flags.modesApi()) { - boolean modified = false; - // These values can always be edited by the app, so we apply changes immediately. - if (isNew) { - rule.id = ZenModeConfig.newRuleId(); - rule.creationTime = mClock.millis(); - rule.component = azr.getOwner(); - rule.pkg = pkg; - modified = true; - } - // Allow updating the CPS backing system rules (e.g. for custom manual -> schedule) - if (Flags.modesUi() - && (origin == ORIGIN_SYSTEM || origin == ORIGIN_USER_IN_SYSTEMUI) - && Objects.equals(rule.pkg, SystemZenRules.PACKAGE_ANDROID) - && !Objects.equals(rule.component, azr.getOwner())) { - rule.component = azr.getOwner(); - modified = true; - } + boolean modified = false; + // These values can always be edited by the app, so we apply changes immediately. + if (isNew) { + rule.id = ZenModeConfig.newRuleId(); + rule.creationTime = mClock.millis(); + rule.component = azr.getOwner(); + rule.pkg = pkg; + modified = true; + } - if (Flags.modesUi()) { - if (!azr.isEnabled() && (isNew || rule.enabled)) { - // Creating a rule as disabled, or disabling a previously enabled rule. - // Record whodunit. - rule.disabledOrigin = origin; - } else if (azr.isEnabled()) { - // Enabling or previously enabled. Clear disabler. - rule.disabledOrigin = ORIGIN_UNKNOWN; - } - } + // Allow updating the CPS backing system rules (e.g. for custom manual -> schedule) + if (Flags.modesUi() + && (origin == ORIGIN_SYSTEM || origin == ORIGIN_USER_IN_SYSTEMUI) + && Objects.equals(rule.pkg, SystemZenRules.PACKAGE_ANDROID) + && !Objects.equals(rule.component, azr.getOwner())) { + rule.component = azr.getOwner(); + modified = true; + } - if (!Objects.equals(rule.conditionId, azr.getConditionId())) { - rule.conditionId = azr.getConditionId(); - modified = true; - } - // This can be removed when {@link Flags#modesUi} is fully ramped up - final boolean isWatch = - mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); - boolean shouldPreserveCondition = - Flags.modesApi() - && (Flags.modesUi() || isWatch) - && !isNew - && origin == ORIGIN_USER_IN_SYSTEMUI - && rule.enabled == azr.isEnabled() - && rule.conditionId != null - && rule.condition != null - && rule.conditionId.equals(rule.condition.id); - if (!shouldPreserveCondition) { - // Do not update 'modified'. If only this changes we treat it as a no-op updateAZR. - rule.condition = null; - } - - if (rule.enabled != azr.isEnabled()) { - rule.enabled = azr.isEnabled(); - rule.resetConditionOverride(); - modified = true; - } - if (!Objects.equals(rule.configurationActivity, azr.getConfigurationActivity())) { - rule.configurationActivity = azr.getConfigurationActivity(); - modified = true; - } - if (rule.allowManualInvocation != azr.isManualInvocationAllowed()) { - rule.allowManualInvocation = azr.isManualInvocationAllowed(); - modified = true; - } - if (!Flags.modesUi()) { - String iconResName = drawableResIdToResName(rule.pkg, azr.getIconResId()); - if (!Objects.equals(rule.iconResName, iconResName)) { - rule.iconResName = iconResName; - modified = true; - } - } - if (!Objects.equals(rule.triggerDescription, azr.getTriggerDescription())) { - rule.triggerDescription = azr.getTriggerDescription(); - modified = true; + if (Flags.modesUi()) { + if (!azr.isEnabled() && (isNew || rule.enabled)) { + // Creating a rule as disabled, or disabling a previously enabled rule. + // Record whodunit. + rule.disabledOrigin = origin; + } else if (azr.isEnabled()) { + // Enabling or previously enabled. Clear disabler. + rule.disabledOrigin = ORIGIN_UNKNOWN; } - if (rule.type != azr.getType()) { - rule.type = azr.getType(); + } + + if (!Objects.equals(rule.conditionId, azr.getConditionId())) { + rule.conditionId = azr.getConditionId(); + modified = true; + } + // This can be removed when {@link Flags#modesUi} is fully ramped up + final boolean isWatch = + mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); + boolean shouldPreserveCondition = + (Flags.modesUi() || isWatch) + && !isNew + && origin == ORIGIN_USER_IN_SYSTEMUI + && rule.enabled == azr.isEnabled() + && rule.conditionId != null + && rule.condition != null + && rule.conditionId.equals(rule.condition.id); + if (!shouldPreserveCondition) { + // Do not update 'modified'. If only this changes we treat it as a no-op updateAZR. + rule.condition = null; + } + + if (rule.enabled != azr.isEnabled()) { + rule.enabled = azr.isEnabled(); + rule.resetConditionOverride(); + modified = true; + } + if (!Objects.equals(rule.configurationActivity, azr.getConfigurationActivity())) { + rule.configurationActivity = azr.getConfigurationActivity(); + modified = true; + } + if (rule.allowManualInvocation != azr.isManualInvocationAllowed()) { + rule.allowManualInvocation = azr.isManualInvocationAllowed(); + modified = true; + } + if (!Flags.modesUi()) { + String iconResName = drawableResIdToResName(rule.pkg, azr.getIconResId()); + if (!Objects.equals(rule.iconResName, iconResName)) { + rule.iconResName = iconResName; modified = true; } - // TODO: b/310620812 - Remove this once FLAG_MODES_API is inlined. - rule.modified = azr.isModified(); + } + if (!Objects.equals(rule.triggerDescription, azr.getTriggerDescription())) { + rule.triggerDescription = azr.getTriggerDescription(); + modified = true; + } + if (rule.type != azr.getType()) { + rule.type = azr.getType(); + modified = true; + } - // Name is treated differently than other values: - // App is allowed to update name if the name was not modified by the user (even if - // other values have been modified). In this way, if the locale of an app changes, - // i18n of the rule name can still occur even if the user has customized the rule - // contents. - String previousName = rule.name; - if (isNew || doesOriginAlwaysUpdateValues(origin) - || (rule.userModifiedFields & AutomaticZenRule.FIELD_NAME) == 0) { - rule.name = azr.getName(); - modified |= !Objects.equals(rule.name, previousName); - } + // Name is treated differently than other values: + // App is allowed to update name if the name was not modified by the user (even if + // other values have been modified). In this way, if the locale of an app changes, + // i18n of the rule name can still occur even if the user has customized the rule + // contents. + String previousName = rule.name; + if (isNew || doesOriginAlwaysUpdateValues(origin) + || (rule.userModifiedFields & AutomaticZenRule.FIELD_NAME) == 0) { + rule.name = azr.getName(); + modified |= !Objects.equals(rule.name, previousName); + } - // For the remaining values, rules can always have all values updated if: - // * the rule is newly added, or - // * the request comes from an origin that can always update values, like the user, or - // * the rule has not yet been user modified, and thus can be updated by the app. - boolean updateValues = isNew || doesOriginAlwaysUpdateValues(origin) - || rule.canBeUpdatedByApp(); + // For the remaining values, rules can always have all values updated if: + // * the rule is newly added, or + // * the request comes from an origin that can always update values, like the user, or + // * the rule has not yet been user modified, and thus can be updated by the app. + boolean updateValues = isNew || doesOriginAlwaysUpdateValues(origin) + || !rule.isUserModified(); - // For all other values, if updates are not allowed, we discard the update. - if (!updateValues) { - return modified; - } + // For all other values, if updates are not allowed, we discard the update. + if (!updateValues) { + return modified; + } - // Updates the bitmasks if the origin of the change is the user. - boolean updateBitmask = (origin == ORIGIN_USER_IN_SYSTEMUI); + // Updates the bitmasks if the origin of the change is the user. + boolean updateBitmask = (origin == ORIGIN_USER_IN_SYSTEMUI); - if (updateBitmask && !TextUtils.equals(previousName, azr.getName())) { - rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME; + if (updateBitmask && !TextUtils.equals(previousName, azr.getName())) { + rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME; + } + int newZenMode = NotificationManager.zenModeFromInterruptionFilter( + azr.getInterruptionFilter(), Global.ZEN_MODE_OFF); + if (rule.zenMode != newZenMode) { + rule.zenMode = newZenMode; + if (updateBitmask) { + rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER; } - int newZenMode = NotificationManager.zenModeFromInterruptionFilter( - azr.getInterruptionFilter(), Global.ZEN_MODE_OFF); - if (rule.zenMode != newZenMode) { - rule.zenMode = newZenMode; + modified = true; + } + + if (Flags.modesUi()) { + String iconResName = drawableResIdToResName(rule.pkg, azr.getIconResId()); + if (!Objects.equals(rule.iconResName, iconResName)) { + rule.iconResName = iconResName; if (updateBitmask) { - rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER; + rule.userModifiedFields |= AutomaticZenRule.FIELD_ICON; } modified = true; } + } - if (Flags.modesUi()) { - String iconResName = drawableResIdToResName(rule.pkg, azr.getIconResId()); - if (!Objects.equals(rule.iconResName, iconResName)) { - rule.iconResName = iconResName; - if (updateBitmask) { - rule.userModifiedFields |= AutomaticZenRule.FIELD_ICON; - } - modified = true; - } - } - - // Updates the bitmask and values for all policy fields, based on the origin. - modified |= updatePolicy(config, rule, azr.getZenPolicy(), updateBitmask, isNew); + // Updates the bitmask and values for all policy fields, based on the origin. + modified |= updatePolicy(config, rule, azr.getZenPolicy(), updateBitmask, isNew); - // Updates the bitmask and values for all device effect fields, based on the origin. - modified |= updateZenDeviceEffects(rule, azr.getDeviceEffects(), - origin == ORIGIN_APP, updateBitmask); + // Updates the bitmask and values for all device effect fields, based on the origin. + modified |= updateZenDeviceEffects(rule, azr.getDeviceEffects(), + origin == ORIGIN_APP, updateBitmask); - return modified; - } else { - if (rule.enabled != azr.isEnabled()) { - rule.resetConditionOverride(); - } - rule.name = azr.getName(); - rule.condition = null; - rule.conditionId = azr.getConditionId(); - rule.enabled = azr.isEnabled(); - rule.modified = azr.isModified(); - rule.zenPolicy = azr.getZenPolicy(); - rule.zenMode = NotificationManager.zenModeFromInterruptionFilter( - azr.getInterruptionFilter(), Global.ZEN_MODE_OFF); - rule.configurationActivity = azr.getConfigurationActivity(); - - if (isNew) { - rule.id = ZenModeConfig.newRuleId(); - rule.creationTime = System.currentTimeMillis(); - rule.component = azr.getOwner(); - rule.pkg = pkg; - } - - // Only the MODES_API path cares about the result, so just return whatever here. - return true; - } + return modified; } /** @@ -1629,32 +1537,21 @@ public class ZenModeHelper { } private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) { - AutomaticZenRule azr; - if (Flags.modesApi()) { - azr = new AutomaticZenRule.Builder(rule.name, rule.conditionId) - .setManualInvocationAllowed(rule.allowManualInvocation) - .setPackage(rule.pkg) - .setCreationTime(rule.creationTime) - .setIconResId(drawableResNameToResId(rule.pkg, rule.iconResName)) - .setType(rule.type) - .setZenPolicy(rule.zenPolicy) - .setDeviceEffects(rule.zenDeviceEffects) - .setEnabled(rule.enabled) - .setInterruptionFilter( - NotificationManager.zenModeToInterruptionFilter(rule.zenMode)) - .setOwner(rule.component) - .setConfigurationActivity(rule.configurationActivity) - .setTriggerDescription(rule.triggerDescription) - .build(); - } else { - azr = new AutomaticZenRule(rule.name, rule.component, - rule.configurationActivity, - rule.conditionId, rule.zenPolicy, - NotificationManager.zenModeToInterruptionFilter(rule.zenMode), - rule.enabled, rule.creationTime); - azr.setPackageName(rule.pkg); - } - return azr; + return new AutomaticZenRule.Builder(rule.name, rule.conditionId) + .setManualInvocationAllowed(rule.allowManualInvocation) + .setPackage(rule.pkg) + .setCreationTime(rule.creationTime) + .setIconResId(drawableResNameToResId(rule.pkg, rule.iconResName)) + .setType(rule.type) + .setZenPolicy(rule.zenPolicy) + .setDeviceEffects(rule.zenDeviceEffects) + .setEnabled(rule.enabled) + .setInterruptionFilter( + NotificationManager.zenModeToInterruptionFilter(rule.zenMode)) + .setOwner(rule.component) + .setConfigurationActivity(rule.configurationActivity) + .setTriggerDescription(rule.triggerDescription) + .build(); } // Update only the hasPriorityChannels state (aka areChannelsBypassingDnd) without modifying @@ -1669,12 +1566,12 @@ public class ZenModeHelper { if (config == null) return; // If it already matches, do nothing - if (config.areChannelsBypassingDnd == hasPriorityChannels) { + if (config.hasPriorityChannels == hasPriorityChannels) { return; } ZenModeConfig newConfig = config.copy(); - newConfig.areChannelsBypassingDnd = hasPriorityChannels; + newConfig.hasPriorityChannels = hasPriorityChannels; // The updated calculation of whether there are priority channels is always done by // the system, even if the event causing the calculation had a different origin. setConfigLocked(newConfig, null, ORIGIN_SYSTEM, "updateHasPriorityChannels", @@ -1754,9 +1651,7 @@ public class ZenModeHelper { newRule.zenMode = zenMode; newRule.conditionId = conditionId; newRule.enabler = caller; - if (Flags.modesApi()) { - newRule.allowManualInvocation = true; - } + newRule.allowManualInvocation = true; newConfig.manualRule = newRule; } } @@ -1849,7 +1744,7 @@ public class ZenModeHelper { boolean hasDefaultRules = config.automaticRules.containsAll( ZenModeConfig.getDefaultRuleIds()); - long time = Flags.modesApi() ? mClock.millis() : System.currentTimeMillis(); + long time = mClock.millis(); if (config.automaticRules != null && config.automaticRules.size() > 0) { for (ZenRule automaticRule : config.automaticRules.values()) { if (forRestore) { @@ -1863,7 +1758,7 @@ public class ZenModeHelper { // Upon upgrading to a version with modes_api enabled, keep all behaviors of // rules with null ZenPolicies explicitly as a copy of the global policy. - if (Flags.modesApi() && config.version < ZenModeConfig.XML_VERSION_MODES_API) { + if (config.version < ZenModeConfig.XML_VERSION_MODES_API) { // Keep the manual ("global") policy that from config. ZenPolicy manualRulePolicy = config.getZenPolicy(); if (automaticRule.zenPolicy == null) { @@ -1877,8 +1772,7 @@ public class ZenModeHelper { } } - if (Flags.modesApi() && Flags.modesUi() - && config.version < ZenModeConfig.XML_VERSION_MODES_UI) { + if (Flags.modesUi() && config.version < ZenModeConfig.XML_VERSION_MODES_UI) { // Clear icons from implicit rules. App icons are not suitable for some // surfaces, so juse use a default (the user can select a different one). if (ZenModeConfig.isImplicitRuleId(automaticRule.id)) { @@ -1904,11 +1798,11 @@ public class ZenModeHelper { reason += ", reset to default rules"; } - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { SystemZenRules.maybeUpgradeRules(mContext, config); } - if (Flags.modesApi() && forRestore) { + if (forRestore) { // Note: forBackup doesn't write deletedRules, but just in case. config.deletedRules.clear(); } @@ -1995,7 +1889,7 @@ public class ZenModeHelper { if (config == null) return; final ZenModeConfig newConfig = config.copy(); - if (Flags.modesApi() && !Flags.modesUi()) { + if (!Flags.modesUi()) { // Fix for b/337193321 -- propagate changes to notificationPolicy to rules where // the user cannot edit zen policy to emulate the previous "inheritance". ZenPolicy previousPolicy = ZenAdapters.notificationPolicyToZenPolicy( @@ -2026,6 +1920,7 @@ public class ZenModeHelper { * <ul> * <li>Rule instances whose owner is not installed. * <li>Deleted rules that were deleted more than 30 days ago. + * <li>Implicit rules that haven't been used in 30 days (and have not been customized). * </ul> */ private void cleanUpZenRules() { @@ -2034,17 +1929,20 @@ public class ZenModeHelper { final ZenModeConfig newConfig = mConfig.copy(); deleteRulesWithoutOwner(newConfig.automaticRules); - if (Flags.modesApi()) { - deleteRulesWithoutOwner(newConfig.deletedRules); - for (int i = newConfig.deletedRules.size() - 1; i >= 0; i--) { - ZenRule deletedRule = newConfig.deletedRules.valueAt(i); - if (deletedRule.deletionInstant == null - || deletedRule.deletionInstant.isBefore(keptRuleThreshold)) { - newConfig.deletedRules.removeAt(i); - } + deleteRulesWithoutOwner(newConfig.deletedRules); + + for (int i = newConfig.deletedRules.size() - 1; i >= 0; i--) { + ZenRule deletedRule = newConfig.deletedRules.valueAt(i); + if (deletedRule.deletionInstant == null + || deletedRule.deletionInstant.isBefore(keptRuleThreshold)) { + newConfig.deletedRules.removeAt(i); } } + if (Flags.modesUi() && Flags.modesCleanupImplicit()) { + deleteUnusedImplicitRules(newConfig.automaticRules); + } + if (!newConfig.equals(mConfig)) { setConfigLocked(newConfig, null, ORIGIN_SYSTEM, "cleanUpZenRules", Process.SYSTEM_UID); @@ -2053,7 +1951,7 @@ public class ZenModeHelper { } private void deleteRulesWithoutOwner(ArrayMap<String, ZenRule> ruleList) { - long currentTime = Flags.modesApi() ? mClock.millis() : System.currentTimeMillis(); + long currentTime = mClock.millis(); if (ruleList != null) { for (int i = ruleList.size() - 1; i >= 0; i--) { ZenRule rule = ruleList.valueAt(i); @@ -2070,6 +1968,29 @@ public class ZenModeHelper { } } + private void deleteUnusedImplicitRules(ArrayMap<String, ZenRule> ruleList) { + if (ruleList == null) { + return; + } + Instant deleteIfUnusedSince = mClock.instant().minus(IMPLICIT_RULE_KEPT_FOR); + + for (int i = ruleList.size() - 1; i >= 0; i--) { + ZenRule rule = ruleList.valueAt(i); + if (isImplicitRuleId(rule.id) && !rule.isUserModified()) { + if (rule.lastActivation == null) { + // This rule existed before we started tracking activation time. It *might* be + // in use. Set lastActivation=now so it has some time (IMPLICIT_RULE_KEPT_FOR) + // before being removed if truly unused. + rule.lastActivation = mClock.instant(); + } + + if (rule.lastActivation.isBefore(deleteIfUnusedSince)) { + ruleList.removeAt(i); + } + } + } + } + /** * @return a copy of the zen mode configuration */ @@ -2188,7 +2109,7 @@ public class ZenModeHelper { mZenMode, mConfig, mConsolidatedPolicy); if (!config.equals(mConfig)) { // Schedule broadcasts. Cannot be sent during boot, though. - if (Flags.modesApi() && origin != ORIGIN_INIT) { + if (origin != ORIGIN_INIT) { for (ZenRule rule : config.automaticRules.values()) { ZenRule original = mConfig.automaticRules.get(rule.id); if (original != null) { @@ -2204,6 +2125,20 @@ public class ZenModeHelper { } } + // Update last activation for rules that are being activated. + if (Flags.modesUi() && Flags.modesCleanupImplicit()) { + Instant now = mClock.instant(); + if (!mConfig.isManualActive() && config.isManualActive()) { + config.manualRule.lastActivation = now; + } + for (ZenRule rule : config.automaticRules.values()) { + ZenRule previousRule = mConfig.automaticRules.get(rule.id); + if (rule.isActive() && (previousRule == null || !previousRule.isActive())) { + rule.lastActivation = now; + } + } + } + mConfig = config; dispatchOnConfigChanged(); updateAndApplyConsolidatedPolicyAndDeviceEffects(origin, reason); @@ -2295,7 +2230,7 @@ public class ZenModeHelper { private void applyCustomPolicy(ZenModeConfig config, ZenPolicy policy, ZenRule rule, boolean useManualConfig) { if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { policy.apply(ZenPolicy.getBasePolicyInterruptionFilterNone()); } else { policy.apply(new ZenPolicy.Builder() @@ -2304,7 +2239,7 @@ public class ZenModeHelper { .build()); } } else if (rule.zenMode == Global.ZEN_MODE_ALARMS) { - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { policy.apply(ZenPolicy.getBasePolicyInterruptionFilterAlarms()); } else { policy.apply(new ZenPolicy.Builder() @@ -2317,22 +2252,17 @@ public class ZenModeHelper { } else if (rule.zenPolicy != null) { policy.apply(rule.zenPolicy); } else { - if (Flags.modesApi()) { - if (useManualConfig) { - // manual rule is configured using the settings stored directly in ZenModeConfig - policy.apply(config.getZenPolicy()); - } else { - // under modes_api flag, an active automatic rule with no specified policy - // inherits the device default settings as stored in mDefaultConfig. While the - // rule's policy fields should be set upon creation, this is a fallback to - // catch any that may have fallen through the cracks. - Log.wtf(TAG, "active automatic rule found with no specified policy: " + rule); - policy.apply(Flags.modesUi() - ? mDefaultConfig.getZenPolicy() : config.getZenPolicy()); - } - } else { - // active rule with no specified policy inherits the manual rule config settings + if (useManualConfig) { + // manual rule is configured using the settings stored directly in ZenModeConfig policy.apply(config.getZenPolicy()); + } else { + // An active automatic rule with no specified policy inherits the device default + // settings as stored in mDefaultConfig. While the rule's policy fields should be + // set upon creation, this is a fallback to catch any that may have fallen through + // the cracks. + Log.wtf(TAG, "active automatic rule found with no specified policy: " + rule); + policy.apply(Flags.modesUi() + ? mDefaultConfig.getZenPolicy() : config.getZenPolicy()); } } } @@ -2346,9 +2276,7 @@ public class ZenModeHelper { ZenDeviceEffects.Builder deviceEffectsBuilder = new ZenDeviceEffects.Builder(); if (mConfig.isManualActive()) { applyCustomPolicy(mConfig, policy, mConfig.manualRule, true); - if (Flags.modesApi()) { - deviceEffectsBuilder.add(mConfig.manualRule.zenDeviceEffects); - } + deviceEffectsBuilder.add(mConfig.manualRule.zenDeviceEffects); } for (ZenRule automaticRule : mConfig.automaticRules.values()) { @@ -2356,12 +2284,10 @@ public class ZenModeHelper { // Active rules with INTERRUPTION_FILTER_ALL are not included in consolidated // policy. This is relevant in case some other active rule has a more // restrictive INTERRUPTION_FILTER but a more lenient ZenPolicy! - if (!Flags.modesApi() || automaticRule.zenMode != Global.ZEN_MODE_OFF) { + if (automaticRule.zenMode != Global.ZEN_MODE_OFF) { applyCustomPolicy(mConfig, policy, automaticRule, false); } - if (Flags.modesApi()) { - deviceEffectsBuilder.add(automaticRule.zenDeviceEffects); - } + deviceEffectsBuilder.add(automaticRule.zenDeviceEffects); } } @@ -2380,40 +2306,35 @@ public class ZenModeHelper { ZenLog.traceSetConsolidatedZenPolicy(mConsolidatedPolicy, reason); } - if (Flags.modesApi()) { - // Prevent other rules from applying grayscale if Driving is active (but allow it - // if _Driving itself_ wants grayscale). - if (Flags.modesUi() && preventZenDeviceEffectsWhileDriving()) { - boolean hasActiveDriving = false; - boolean hasActiveDrivingWithGrayscale = false; - for (ZenRule rule : mConfig.automaticRules.values()) { - if (rule.isActive() && rule.type == TYPE_DRIVING) { - hasActiveDriving = true; - if (rule.zenDeviceEffects != null - && rule.zenDeviceEffects.shouldDisplayGrayscale()) { - hasActiveDrivingWithGrayscale = true; - break; // Further rules won't affect decision. - } + // Prevent other rules from applying grayscale if Driving is active (but allow it + // if _Driving itself_ wants grayscale). + if (Flags.modesUi() && preventZenDeviceEffectsWhileDriving()) { + boolean hasActiveDriving = false; + boolean hasActiveDrivingWithGrayscale = false; + for (ZenRule rule : mConfig.automaticRules.values()) { + if (rule.isActive() && rule.type == TYPE_DRIVING) { + hasActiveDriving = true; + if (rule.zenDeviceEffects != null + && rule.zenDeviceEffects.shouldDisplayGrayscale()) { + hasActiveDrivingWithGrayscale = true; + break; // Further rules won't affect decision. } } - if (hasActiveDriving && !hasActiveDrivingWithGrayscale) { - deviceEffectsBuilder.setShouldDisplayGrayscale(false); - } } - - ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build(); - if (!deviceEffects.equals(mConsolidatedDeviceEffects)) { - mConsolidatedDeviceEffects = deviceEffects; - mHandler.postApplyDeviceEffects(origin); + if (hasActiveDriving && !hasActiveDrivingWithGrayscale) { + deviceEffectsBuilder.setShouldDisplayGrayscale(false); } } + + ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build(); + if (!deviceEffects.equals(mConsolidatedDeviceEffects)) { + mConsolidatedDeviceEffects = deviceEffects; + mHandler.postApplyDeviceEffects(origin); + } } } private void applyConsolidatedDeviceEffects(@ConfigOrigin int source) { - if (!Flags.modesApi()) { - return; - } DeviceEffectsApplier applier; ZenDeviceEffects effects; synchronized (mConfigLock) { @@ -2434,10 +2355,8 @@ public class ZenModeHelper { * to the current locale. */ private static void updateDefaultConfig(Context context, ZenModeConfig defaultConfig) { - if (Flags.modesApi()) { - updateDefaultAutomaticRulePolicies(defaultConfig); - } - if (Flags.modesApi() && Flags.modesUi()) { + updateDefaultAutomaticRulePolicies(defaultConfig); + if (Flags.modesUi()) { SystemZenRules.maybeUpgradeRules(context, defaultConfig); } updateRuleStringsForCurrentLocale(context, defaultConfig); @@ -2453,7 +2372,7 @@ public class ZenModeHelper { rule.name = context.getResources() .getString(R.string.zen_mode_default_every_night_name); } - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { SystemZenRules.updateTriggerDescription(context, rule); } } @@ -2462,10 +2381,6 @@ public class ZenModeHelper { // Updates the policies in the default automatic rules (provided via default XML config) to // be fully filled in default values. private static void updateDefaultAutomaticRulePolicies(ZenModeConfig defaultConfig) { - if (!Flags.modesApi()) { - // Should be checked before calling, but just in case. - return; - } ZenPolicy defaultPolicy = defaultConfig.getZenPolicy(); for (ZenRule rule : defaultConfig.automaticRules.values()) { if (ZenModeConfig.getDefaultRuleIds().contains(rule.id) && rule.zenPolicy == null) { @@ -2611,6 +2526,7 @@ public class ZenModeHelper { } } + // TODO: b/368247671 - Delete this method AND default_zen_mode_config.xml when inlining modes_ui private ZenModeConfig readDefaultConfig(Resources resources) { XmlResourceParser parser = null; try { @@ -2649,7 +2565,7 @@ public class ZenModeHelper { events.add(FrameworkStatsLog.buildStatsEvent(DND_MODE_RULE, /* optional int32 user = 1 */ user, /* optional bool enabled = 2 */ config.isManualActive(), - /* optional bool channels_bypassing = 3 */ config.areChannelsBypassingDnd, + /* optional bool channels_bypassing = 3 */ config.hasPriorityChannels, /* optional LoggedZenMode zen_mode = 4 */ ROOT_CONFIG, /* optional string id = 5 */ "", // empty for root config /* optional int32 uid = 6 */ Process.SYSTEM_UID, // system owns root config @@ -2924,9 +2840,6 @@ public class ZenModeHelper { * ({@link #addAutomaticZenRule}, {@link #removeAutomaticZenRule}, etc, makes sense. */ private static void checkManageRuleOrigin(String method, @ConfigOrigin int origin) { - if (!Flags.modesApi()) { - return; - } checkArgument(origin == ORIGIN_APP || origin == ORIGIN_SYSTEM || origin == ORIGIN_USER_IN_SYSTEMUI, "Expected one of ORIGIN_APP, ORIGIN_SYSTEM, or " @@ -2939,9 +2852,6 @@ public class ZenModeHelper { * {@link #setAutomaticZenRuleStateFromConditionProvider} makes sense. */ private static void checkSetRuleStateOrigin(String method, @ConfigOrigin int origin) { - if (!Flags.modesApi()) { - return; - } checkArgument(origin == ORIGIN_APP || origin == ORIGIN_USER_IN_APP || origin == ORIGIN_SYSTEM || origin == ORIGIN_USER_IN_SYSTEMUI, "Expected one of ORIGIN_APP, ORIGIN_USER_IN_APP, ORIGIN_SYSTEM, or " diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 048f2b6b0cbc..76cd5c88b388 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -210,3 +210,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "managed_services_concurrent_multiuser" + namespace: "systemui" + description: "Enables ManagedServices to support Concurrent multi user environment" + bug: "380297485" +} diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java index d538bb876b64..c3af578de369 100644 --- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java +++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java @@ -176,16 +176,13 @@ public class BackgroundInstallControlService extends SystemService { if (Flags.bicClient()) { mService.enforceCallerPermissions(); } - if (!Build.IS_DEBUGGABLE) { - return mService.getBackgroundInstalledPackages(flags, userId); - } // The debug.transparency.bg-install-apps (only works for debuggable builds) // is used to set mock list of background installed apps for testing. // The list of apps' names is delimited by ",". // TODO: Remove after migrating test to new background install method using // {@link BackgroundInstallControlCallbackHelperTest}.installPackage b/310983905 String propertyString = SystemProperties.get("debug.transparency.bg-install-apps"); - if (TextUtils.isEmpty(propertyString)) { + if (TextUtils.isEmpty(propertyString) || !Build.IS_DEBUGGABLE) { return mService.getBackgroundInstalledPackages(flags, userId); } else { return mService.getMockBackgroundInstalledPackages(propertyString); @@ -219,10 +216,27 @@ public class BackgroundInstallControlService extends SystemService { PackageManager.PackageInfoFlags.of(flags), userId); initBackgroundInstalledPackages(); + if(Build.IS_DEBUGGABLE) { + StringBuilder sb = new StringBuilder(); + sb.append("Tracked background installed package size: ") + .append(mBackgroundInstalledPackages.size()) + .append("\n"); + for (int i = 0; i < mBackgroundInstalledPackages.size(); ++i) { + int installingUserId = mBackgroundInstalledPackages.keyAt(i); + mBackgroundInstalledPackages.get(installingUserId).forEach(pkgName -> + sb.append("userId: ").append(installingUserId) + .append(", name: ").append(pkgName).append("\n")); + } + Slog.d(TAG, "Tracked background installed package: " + sb.toString()); + } + ListIterator<PackageInfo> iter = packages.listIterator(); while (iter.hasNext()) { String packageName = iter.next().packageName; if (!mBackgroundInstalledPackages.contains(userId, packageName)) { + if(Build.IS_DEBUGGABLE) { + Slog.d(TAG, packageName + " is not tracked, removing"); + } iter.remove(); } } @@ -284,6 +298,9 @@ public class BackgroundInstallControlService extends SystemService { } void handlePackageAdd(String packageName, int userId) { + if(Build.IS_DEBUGGABLE) { + Slog.d(TAG, "handlePackageAdd: checking " + packageName); + } ApplicationInfo appInfo = null; try { appInfo = @@ -302,7 +319,7 @@ public class BackgroundInstallControlService extends SystemService { installerPackageName = installInfo.getInstallingPackageName(); initiatingPackageName = installInfo.getInitiatingPackageName(); } catch (PackageManager.NameNotFoundException e) { - Slog.w(TAG, "Package's installer not found " + packageName); + Slog.w(TAG, "Package's installer not found: " + packageName); return; } @@ -314,6 +331,10 @@ public class BackgroundInstallControlService extends SystemService { VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, userId) != PERMISSION_GRANTED) { + if(Build.IS_DEBUGGABLE) { + Slog.d(TAG, "handlePackageAdd " + packageName + ": installer doesn't " + + "have INSTALL_PACKAGES permission, skipping"); + } return; } @@ -324,6 +345,10 @@ public class BackgroundInstallControlService extends SystemService { if (installedByAdb(initiatingPackageName) || wasForegroundInstallation(installerPackageName, userId, installTimestamp)) { + if(Build.IS_DEBUGGABLE) { + Slog.d(TAG, "handlePackageAdd " + packageName + ": is installed by ADB or was " + + "foreground installation, skipping"); + } return; } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 15688c0f7366..28117470e7a3 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -204,6 +204,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; @@ -1023,6 +1024,7 @@ final class InstallPackageHelper { */ void installPackagesTraced(List<InstallRequest> requests, MoveInfo moveInfo) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages"); + boolean pendingForDexopt = false; boolean success = false; final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size()); final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size()); @@ -1036,17 +1038,41 @@ final class InstallPackageHelper { if (reconciledPackages == null) { return; } + if (renameAndUpdatePaths(requests)) { // rename before dexopt because art will encoded the path in the odex/vdex file if (Flags.improveInstallFreeze()) { - prepPerformDexoptIfNeeded(reconciledPackages); - } - if (commitInstallPackages(reconciledPackages)) { - success = true; + pendingForDexopt = true; + final Runnable actionsAfterDexopt = () -> + doPostDexopt(reconciledPackages, requests, + createdAppId, moveInfo, acquireTime); + prepPerformDexoptIfNeeded(reconciledPackages, actionsAfterDexopt); + } else { + if (commitInstallPackages(reconciledPackages)) { + success = true; + } } } } } finally { + if (!pendingForDexopt) { + completeInstallProcess(requests, createdAppId, success); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + doPostInstall(requests, moveInfo); + releaseWakeLock(acquireTime, requests.size()); + } + } + } + + void doPostDexopt(List<ReconciledPackage> reconciledPackages, + List<InstallRequest> requests, Map<String, Boolean> createdAppId, + MoveInfo moveInfo, long acquireTime) { + boolean success = false; + try { + if (commitInstallPackages(reconciledPackages)) { + success = true; + } + } finally { completeInstallProcess(requests, createdAppId, success); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); doPostInstall(requests, moveInfo); @@ -1123,7 +1149,7 @@ final class InstallPackageHelper { throws PackageManagerException { final int userId = installRequest.getUserId(); if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT - && !mPm.mUserManager.exists(userId)) { + && !ArrayUtils.contains(allUsers, userId)) { throw new PackageManagerException(PackageManagerException.INTERNAL_ERROR_MISSING_USER, "User " + userId + " doesn't exist or has been removed"); } @@ -1155,7 +1181,9 @@ final class InstallPackageHelper { } } - private void prepPerformDexoptIfNeeded(List<ReconciledPackage> reconciledPackages) { + private void prepPerformDexoptIfNeeded(List<ReconciledPackage> reconciledPackages, + Runnable actionsAfterDexopt) { + List<CompletableFuture<Void>> completableFutures = new ArrayList<>(); for (ReconciledPackage reconciledPkg : reconciledPackages) { final InstallRequest request = reconciledPkg.mInstallRequest; // prepare profiles @@ -1171,6 +1199,7 @@ final class InstallPackageHelper { mSharedLibraries.executeSharedLibrariesUpdate(request.getParsedPackage(), ps, null, null, reconciledPkg.mCollectedSharedLibraryInfos, allUsers); } + try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) { final int[] newUsers = getNewUsers(request, allUsers); // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088) @@ -1182,11 +1211,22 @@ final class InstallPackageHelper { } } catch (PackageManagerException e) { request.setError(e.error, e.getMessage()); - return; + break; } request.setKeepArtProfile(true); - // TODO(b/388159696): Use performDexoptIfNeededAsync. - DexOptHelper.performDexoptIfNeeded(request, mDexManager, null /* installLock */); + + CompletableFuture<Void> future = + DexOptHelper.performDexoptIfNeededAsync(request, mDexManager); + completableFutures.add(future); + } + + if (!completableFutures.isEmpty()) { + CompletableFuture<Void> allFutures = + CompletableFuture.allOf( + completableFutures.toArray(CompletableFuture[]::new)); + var unused = allFutures.thenRun(() -> mPm.mHandler.post(actionsAfterDexopt)); + } else { + actionsAfterDexopt.run(); } } @@ -2759,6 +2799,7 @@ final class InstallPackageHelper { | Installer.FLAG_CLEAR_CODE_CACHE_ONLY); } + // run synchronous dexopt if the freeze improvement is not supported DexOptHelper.performDexoptIfNeeded( installRequest, mDexManager, mPm.mInstallLock.getRawLock()); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index e1fcc6650650..2d0bb258e89f 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -805,22 +805,20 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } - if (Flags.recoverabilityDetection()) { - if (params.rollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH - || params.rollbackImpactLevel - == PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL) { - if ((params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) { - throw new IllegalArgumentException( - "Can't set rollbackImpactLevel when rollback is not enabled"); - } - if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException( - "Setting rollbackImpactLevel requires the MANAGE_ROLLBACKS permission"); - } - } else if (params.rollbackImpactLevel < 0) { - throw new IllegalArgumentException("rollbackImpactLevel can't be negative."); + if (params.rollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH + || params.rollbackImpactLevel + == PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL) { + if ((params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) { + throw new IllegalArgumentException( + "Can't set rollbackImpactLevel when rollback is not enabled"); + } + if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + "Setting rollbackImpactLevel requires the MANAGE_ROLLBACKS permission"); } + } else if (params.rollbackImpactLevel < 0) { + throw new IllegalArgumentException("rollbackImpactLevel can't be negative."); } boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0; diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 352985d5a023..136cb1259113 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -948,6 +948,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { break; case MSG_ON_NATIVE_LIBS_EXTRACTED: handleOnNativeLibsExtracted(); + break; } return true; diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index aa235c2258ac..cf598e89c988 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -3561,9 +3561,6 @@ class PackageManagerShellCommand extends ShellCommand { sessionParams.setEnableRollback(true, rollbackStrategy); break; case "--rollback-impact-level": - if (!Flags.recoverabilityDetection()) { - throw new IllegalArgumentException("Unknown option " + opt); - } int rollbackImpactLevel = Integer.parseInt(peekNextArg()); if (rollbackImpactLevel < PackageManager.ROLLBACK_USER_IMPACT_LOW || rollbackImpactLevel @@ -4775,11 +4772,9 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" --full: cause the app to be installed as a non-ephemeral full app"); pw.println(" --enable-rollback: enable rollbacks for the upgrade."); pw.println(" 0=restore (default), 1=wipe, 2=retain"); - if (Flags.recoverabilityDetection()) { - pw.println( - " --rollback-impact-level: set device impact required for rollback."); - pw.println(" 0=low (default), 1=high, 2=manual only"); - } + pw.println( + " --rollback-impact-level: set device impact required for rollback."); + pw.println(" 0=low (default), 1=high, 2=manual only"); pw.println(" --install-location: force the install location:"); pw.println(" 0=auto, 1=internal only, 2=prefer external"); pw.println(" --install-reason: indicates why the app is being installed:"); diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java index 045d4db0a1f1..346327d1fa74 100644 --- a/services/core/java/com/android/server/pm/ShortcutLauncher.java +++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java @@ -453,7 +453,7 @@ class ShortcutLauncher extends ShortcutPackageItem { @Override protected File getShortcutPackageItemFile() { final File path = new File(mShortcutUser.mService.injectUserDataPath( - mShortcutUser.getUserId()), ShortcutUser.DIRECTORY_LUANCHERS); + mShortcutUser.getUserId()), ShortcutUser.DIRECTORY_LAUNCHERS); // Package user id and owner id can have different values for ShortcutLaunchers. Adding // user Id to the file name to create a unique path. Owner id is used in the root path. final String fileName = getPackageName() + getPackageUserId() + ".xml"; diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 373c1ed3c386..d3513053caf3 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -16,7 +16,9 @@ package com.android.server.pm; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; -import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; + +import static com.android.server.pm.ShortcutUser.DIRECTORY_LAUNCHERS; +import static com.android.server.pm.ShortcutUser.DIRECTORY_PACKAGES; import android.Manifest.permission; import android.annotation.IntDef; @@ -94,7 +96,6 @@ import android.os.ShellCommand; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; -import android.provider.DeviceConfig; import android.text.TextUtils; import android.text.format.TimeMigrationUtils; import android.util.ArraySet; @@ -112,7 +113,6 @@ import android.view.IWindowManager; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.logging.MetricsLogger; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; @@ -155,7 +155,6 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.regex.Pattern; -import java.util.stream.Collectors; /** * TODO: @@ -171,7 +170,7 @@ public class ShortcutService extends IShortcutService.Stub { static final boolean DEBUG = false; // STOPSHIP if true static final boolean DEBUG_LOAD = false; // STOPSHIP if true static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true - static final boolean DEBUG_REBOOT = Build.IS_DEBUGGABLE; + static final boolean DEBUG_REBOOT = false; // STOPSHIP if true @VisibleForTesting static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day @@ -292,7 +291,8 @@ public class ShortcutService extends IShortcutService.Stub { final Context mContext; - private final Object mServiceLock = new Object(); + @VisibleForTesting + final Object mServiceLock = new Object(); private final Object mNonPersistentUsersLock = new Object(); private final Object mWtfLock = new Object(); @@ -982,7 +982,7 @@ public class ShortcutService extends IShortcutService.Stub { } @VisibleForTesting - void saveBaseState() { + void injectSaveBaseState() { try (ResilientAtomicFile file = getBaseStateFile()) { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "Saving to " + file.getBaseFile()); @@ -994,18 +994,7 @@ public class ShortcutService extends IShortcutService.Stub { outs = file.startWrite(); } - // Write to XML - TypedXmlSerializer out = Xml.resolveSerializer(outs); - out.startDocument(null, true); - out.startTag(null, TAG_ROOT); - - // Body. - // No locking required. Ok to add lock later if we save more data. - writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime.get()); - - // Epilogue. - out.endTag(null, TAG_ROOT); - out.endDocument(); + saveBaseStateAsXml(outs); // Close. injectFinishWrite(file, outs); @@ -1016,10 +1005,32 @@ public class ShortcutService extends IShortcutService.Stub { } } + @VisibleForTesting + protected void saveBaseStateAsXml(OutputStream outs) throws IOException { + // Write to XML + TypedXmlSerializer out = Xml.resolveSerializer(outs); + out.startDocument(null, true); + out.startTag(null, TAG_ROOT); + + // Body. + // No locking required. Ok to add lock later if we save more data. + writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime.get()); + + // Epilogue. + out.endTag(null, TAG_ROOT); + out.endDocument(); + } + @GuardedBy("mServiceLock") private void loadBaseStateLocked() { mRawLastResetTime.set(0); + injectLoadBaseState(); + // Adjust the last reset time. + getLastResetTimeLocked(); + } + @VisibleForTesting + protected void injectLoadBaseState() { try (ResilientAtomicFile file = getBaseStateFile()) { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "Loading from " + file.getBaseFile()); @@ -1030,34 +1041,7 @@ public class ShortcutService extends IShortcutService.Stub { if (in == null) { throw new FileNotFoundException(file.getBaseFile().getAbsolutePath()); } - - TypedXmlPullParser parser = Xml.resolvePullParser(in); - - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { - if (type != XmlPullParser.START_TAG) { - continue; - } - final int depth = parser.getDepth(); - // Check the root tag - final String tag = parser.getName(); - if (depth == 1) { - if (!TAG_ROOT.equals(tag)) { - Slog.v(TAG, "Invalid root tag: " + tag); - return; - } - continue; - } - // Assume depth == 2 - switch (tag) { - case TAG_LAST_RESET_TIME: - mRawLastResetTime.set(parseLongAttribute(parser, ATTR_VALUE)); - break; - default: - Slog.v(TAG, "Invalid tag: " + tag); - break; - } - } + loadBaseStateAsXml(in); } catch (FileNotFoundException e) { // Use the default } catch (IOException | XmlPullParserException e) { @@ -1067,8 +1051,38 @@ public class ShortcutService extends IShortcutService.Stub { return; } } - // Adjust the last reset time. - getLastResetTimeLocked(); + } + + @VisibleForTesting + protected void loadBaseStateAsXml(InputStream in) + throws IOException, XmlPullParserException { + TypedXmlPullParser parser = Xml.resolvePullParser(in); + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + // Check the root tag + final String tag = parser.getName(); + if (depth == 1) { + if (!TAG_ROOT.equals(tag)) { + Slog.v(TAG, "Invalid root tag: " + tag); + return; + } + continue; + } + // Assume depth == 2 + switch (tag) { + case TAG_LAST_RESET_TIME: + mRawLastResetTime.set(parseLongAttribute(parser, ATTR_VALUE)); + break; + default: + Slog.v(TAG, "Invalid tag: " + tag); + break; + } + } } @VisibleForTesting @@ -1083,7 +1097,8 @@ public class ShortcutService extends IShortcutService.Stub { "user shortcut", null); } - private void saveUser(@UserIdInt int userId) { + @VisibleForTesting + protected void injectSaveUser(@UserIdInt int userId) { try (ResilientAtomicFile file = getUserFile(userId)) { FileOutputStream os = null; try { @@ -1092,8 +1107,14 @@ public class ShortcutService extends IShortcutService.Stub { } synchronized (mServiceLock) { + // Since we are not handling package deletion yet, or any single package + // changes, just clean the directory and rewrite all the ShortcutPackageItems. + final File root = injectUserDataPath(userId); + FileUtils.deleteContents(new File(root, DIRECTORY_PACKAGES)); + FileUtils.deleteContents(new File(root, DIRECTORY_LAUNCHERS)); os = file.startWrite(); saveUserInternalLocked(userId, os, /* forBackup= */ false); + getUserShortcutsLocked(userId).scheduleSaveAllLaunchersAndPackages(); } injectFinishWrite(file, os); @@ -1109,8 +1130,9 @@ public class ShortcutService extends IShortcutService.Stub { getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger); } + @VisibleForTesting @GuardedBy("mServiceLock") - private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os, + protected void saveUserInternalLocked(@UserIdInt int userId, OutputStream os, boolean forBackup) throws IOException, XmlPullParserException { // Write to XML @@ -1138,8 +1160,9 @@ public class ShortcutService extends IShortcutService.Stub { Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth)); } + @VisibleForTesting @Nullable - private ShortcutUser loadUserLocked(@UserIdInt int userId) { + protected ShortcutUser injectLoadUserLocked(@UserIdInt int userId) { try (ResilientAtomicFile file = getUserFile(userId)) { FileInputStream in = null; try { @@ -1157,12 +1180,13 @@ public class ShortcutService extends IShortcutService.Stub { } catch (Exception e) { // Remove corrupted file and retry. file.failRead(in, e); - return loadUserLocked(userId); + return injectLoadUserLocked(userId); } } } - private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, + @VisibleForTesting + protected ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, boolean fromBackup) throws XmlPullParserException, IOException, InvalidFileFormatException { @@ -1240,9 +1264,9 @@ public class ShortcutService extends IShortcutService.Stub { for (int i = dirtyUserIds.size() - 1; i >= 0; i--) { final int userId = dirtyUserIds.get(i); if (userId == UserHandle.USER_NULL) { // USER_NULL for base state. - saveBaseState(); + injectSaveBaseState(); } else { - saveUser(userId); + injectSaveUser(userId); } } } catch (Exception e) { @@ -1349,7 +1373,7 @@ public class ShortcutService extends IShortcutService.Stub { ShortcutUser userPackages = mUsers.get(userId); if (userPackages == null) { - userPackages = loadUserLocked(userId); + userPackages = injectLoadUserLocked(userId); if (userPackages == null) { userPackages = new ShortcutUser(this, userId); } @@ -1430,8 +1454,9 @@ public class ShortcutService extends IShortcutService.Stub { * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap * saves are going on. */ + @VisibleForTesting @GuardedBy("mServiceLock") - private void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) { + void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) { if (DEBUG) { Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId); } @@ -2755,7 +2780,7 @@ public class ShortcutService extends IShortcutService.Stub { getPackageShortcutsLocked(packageName, userId) .resetRateLimitingForCommandLineNoSaving(); } - saveUser(userId); + injectSaveUser(userId); } // We override this method in unit tests to do a simpler check. @@ -4407,7 +4432,7 @@ public class ShortcutService extends IShortcutService.Stub { pw.println(); }); } - saveUser(userId); + injectSaveUser(userId); } // === Dump === diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index bc8cc7ba29af..632fd16a32f7 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -21,16 +21,12 @@ import android.annotation.UserIdInt; import android.content.pm.ShortcutManager; import android.content.pm.UserPackage; import android.metrics.LogMaker; -import android.os.Binder; -import android.os.FileUtils; -import android.os.UserHandle; import android.text.TextUtils; import android.text.format.Formatter; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -49,8 +45,6 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.IOException; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -63,7 +57,7 @@ class ShortcutUser { private static final String TAG = ShortcutService.TAG; static final String DIRECTORY_PACKAGES = "packages"; - static final String DIRECTORY_LUANCHERS = "launchers"; + static final String DIRECTORY_LAUNCHERS = "launchers"; static final String TAG_ROOT = "user"; private static final String TAG_LAUNCHER = "launcher"; @@ -322,41 +316,43 @@ class ShortcutUser { mService.injectBuildFingerprint()); } - if (!forBackup) { - // Since we are not handling package deletion yet, or any single package changes, just - // clean the directory and rewrite all the ShortcutPackageItems. - final File root = mService.injectUserDataPath(mUserId); - FileUtils.deleteContents(new File(root, DIRECTORY_PACKAGES)); - FileUtils.deleteContents(new File(root, DIRECTORY_LUANCHERS)); - } // Can't use forEachPackageItem due to the checked exceptions. + if (forBackup) { + int size = mLaunchers.size(); + for (int i = 0; i < size; i++) { + saveShortcutPackageItem(out, mLaunchers.valueAt(i)); + } + size = mPackages.size(); + for (int i = 0; i < size; i++) { + saveShortcutPackageItem(out, mPackages.valueAt(i)); + } + } + + out.endTag(null, TAG_ROOT); + } + + void scheduleSaveAllLaunchersAndPackages() { { final int size = mLaunchers.size(); for (int i = 0; i < size; i++) { - saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup); + mLaunchers.valueAt(i).scheduleSave(); } } { final int size = mPackages.size(); for (int i = 0; i < size; i++) { - saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup); + mPackages.valueAt(i).scheduleSave(); } } - - out.endTag(null, TAG_ROOT); } - private void saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi, - boolean forBackup) throws IOException, XmlPullParserException { - if (forBackup) { - if (spi.getPackageUserId() != spi.getOwnerUserId()) { - return; // Don't save cross-user information. - } - spi.waitForBitmapSaves(); - spi.saveToXml(out, forBackup); - } else { - spi.scheduleSave(); + private void saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi) + throws IOException, XmlPullParserException { + if (spi.getPackageUserId() != spi.getOwnerUserId()) { + return; // Don't save cross-user information. } + spi.waitForBitmapSaves(); + spi.saveToXml(out, true /* forBackup */); } public static ShortcutUser loadFromXml(ShortcutService s, TypedXmlPullParser parser, int userId, @@ -429,7 +425,7 @@ class ShortcutUser { } }); - forMainFilesIn(new File(root, DIRECTORY_LUANCHERS), (File f) -> { + forMainFilesIn(new File(root, DIRECTORY_LAUNCHERS), (File f) -> { final ShortcutLauncher sl = ShortcutLauncher.loadFromFile(f, ret, userId, fromBackup); if (sl != null) { diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java index dd60a155f2fb..8510ee70cc56 100644 --- a/services/core/java/com/android/server/pm/VerifyingSession.java +++ b/services/core/java/com/android/server/pm/VerifyingSession.java @@ -179,8 +179,7 @@ final class VerifyingSession { // Perform package verification and enable rollback (unless we are simply moving the // package). if (!mOriginInfo.mExisting) { - final boolean verifyForRollback = Flags.recoverabilityDetection() - ? !isARollback() : true; + final boolean verifyForRollback = !isARollback(); if (!isApex() && !isArchivedInstallation() && verifyForRollback) { // TODO(b/182426975): treat APEX as APK when APK verification is concerned sendApkVerificationRequest(pkgLite); diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 44d787f790cf..c31c287017c3 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -82,12 +82,10 @@ import android.util.LongSparseLongArray; import android.util.Slog; import android.util.SparseBooleanArray; -import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.infra.AndroidFuture; -import com.android.internal.policy.AttributeCache; import com.android.internal.util.IntPair; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; @@ -167,6 +165,7 @@ public final class PermissionPolicyService extends SystemService { private Context mContext; private PackageManagerInternal mPackageManagerInternal; private PermissionManagerServiceInternal mPermissionManagerInternal; + private ActivityTaskManagerInternal mActivityTaskManagerInternal; private NotificationManagerInternal mNotificationManager; private TelephonyManager mTelephonyManager; private final KeyguardManager mKeyguardManager; @@ -189,6 +188,7 @@ public final class PermissionPolicyService extends SystemService { PackageManagerInternal.class); mPermissionManagerInternal = LocalServices.getService( PermissionManagerServiceInternal.class); + mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); @@ -1154,7 +1154,7 @@ public final class PermissionPolicyService extends SystemService { activityInfo.packageName, info.getCallingPackage(), info.getIntent(), info.getCheckedOptions(), activityInfo.name, true) - || isNoDisplayActivity(activityInfo)) { + || isNoDisplayActivity(activityInfo, info.getUserId())) { return; } UserHandle user = UserHandle.of(taskInfo.userId); @@ -1170,9 +1170,7 @@ public final class PermissionPolicyService extends SystemService { }; private void onActivityManagerReady() { - ActivityTaskManagerInternal atm = - LocalServices.getService(ActivityTaskManagerInternal.class); - atm.registerActivityStartInterceptor( + mActivityTaskManagerInternal.registerActivityStartInterceptor( ActivityInterceptorCallback.PERMISSION_POLICY_ORDERED_ID, mActivityInterceptorCallback); } @@ -1227,20 +1225,14 @@ public final class PermissionPolicyService extends SystemService { null, activityName, false); } - private boolean isNoDisplayActivity(@NonNull ActivityInfo aInfo) { + private boolean isNoDisplayActivity(@NonNull ActivityInfo aInfo, int userId) { final int themeResource = aInfo.getThemeResource(); if (themeResource == Resources.ID_NULL) { return false; } - boolean noDisplay = false; - final AttributeCache.Entry ent = AttributeCache.instance() - .get(aInfo.packageName, themeResource, R.styleable.Window, 0); - if (ent != null) { - noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false); - } - - return noDisplay; + return mActivityTaskManagerInternal.isNoDisplay(aInfo.packageName, themeResource, + userId); } /** diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 4153cd1be0a6..980fb155999e 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1168,8 +1168,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mContext.getContentResolver(), Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, mCurrentUserId) == 1; - return mUserManagerInternal.isUserUnlocked(mCurrentUserId) && hubEnabled - && mDreamManagerInternal.dreamConditionActive(); + return mUserManagerInternal != null && mUserManagerInternal.isUserUnlocked(mCurrentUserId) + && hubEnabled && mDreamManagerInternal.dreamConditionActive(); } @VisibleForTesting @@ -1260,7 +1260,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mContext.getContentResolver(), Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, mCurrentUserId) == 1; - if (mDreamManagerInternal.isDreaming() || isKeyguardShowing()) { + if ((mDreamManagerInternal != null && mDreamManagerInternal.isDreaming()) + || isKeyguardShowing()) { // If the device is already dreaming or on keyguard, go to sleep. sleepDefaultDisplayFromPowerButton(eventTime, 0); break; @@ -2339,6 +2340,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { WindowWakeUpPolicy getWindowWakeUpPolicy() { return new WindowWakeUpPolicy(mContext); } + + DreamManagerInternal getDreamManagerInternal() { + return LocalServices.getService(DreamManagerInternal.class); + } } /** {@inheritDoc} */ @@ -2357,7 +2362,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mInputManager = mContext.getSystemService(InputManager.class); mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); - mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class); + mDreamManagerInternal = injector.getDreamManagerInternal(); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mSensorPrivacyManager = mContext.getSystemService(SensorPrivacyManager.class); @@ -3713,15 +3718,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; case KeyEvent.KEYCODE_N: if (firstDown && event.isMetaPressed()) { - if (event.isCtrlPressed()) { - sendSystemKeyToStatusBarAsync(event); - notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES); - } else { - toggleNotificationPanel(); - notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL); - } + toggleNotificationPanel(); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL); return true; } break; diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 8fae875eb29b..e3eced252d1f 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -3379,7 +3379,7 @@ public final class PowerManagerService extends SystemService } changed = sleepPowerGroupLocked(powerGroup, time, PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, Process.SYSTEM_UID); - } else if (shouldNapAtBedTimeLocked()) { + } else if (shouldNapAtBedTimeLocked(powerGroup)) { changed = dreamPowerGroupLocked(powerGroup, time, Process.SYSTEM_UID, /* allowWake= */ false); } else { @@ -3395,7 +3395,10 @@ public final class PowerManagerService extends SystemService * activity timeout has expired and it's bedtime. */ @GuardedBy("mLock") - private boolean shouldNapAtBedTimeLocked() { + private boolean shouldNapAtBedTimeLocked(PowerGroup powerGroup) { + if (!powerGroup.supportsSandmanLocked()) { + return false; + } return mDreamsActivateOnSleepSetting || (mDreamsActivateOnDockSetting && mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) @@ -3617,9 +3620,10 @@ public final class PowerManagerService extends SystemService if (!mDreamsDisabledByAmbientModeSuppressionConfig) { return; } + final PowerGroup defaultPowerGroup = mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP); if (!isSuppressed && mIsPowered && mDreamsSupportedConfig && mDreamsEnabledSetting - && shouldNapAtBedTimeLocked() && isItBedTimeYetLocked( - mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP))) { + && shouldNapAtBedTimeLocked(defaultPowerGroup) + && isItBedTimeYetLocked(defaultPowerGroup)) { napInternal(SystemClock.uptimeMillis(), Process.SYSTEM_UID, /* allowWake= */ true); } else if (isSuppressed) { mDirty |= DIRTY_SETTINGS; diff --git a/services/core/java/com/android/server/power/stats/format/ScreenPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/ScreenPowerStatsLayout.java index 6f6a7ff5064a..ca2237562fe1 100644 --- a/services/core/java/com/android/server/power/stats/format/ScreenPowerStatsLayout.java +++ b/services/core/java/com/android/server/power/stats/format/ScreenPowerStatsLayout.java @@ -29,7 +29,8 @@ import com.android.internal.os.PowerStats; public class ScreenPowerStatsLayout extends PowerStatsLayout { private static final String EXTRA_DEVICE_SCREEN_COUNT = "dsc"; private static final String EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION = "dsd"; - private static final String EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS = "dbd"; + private static final String EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS = "dbds"; + private static final String EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS_COMPAT = "dbd"; private static final String EXTRA_DEVICE_DOZE_DURATION_POSITION = "ddd"; private static final String EXTRA_DEVICE_DOZE_POWER_POSITION = "ddp"; private static final String EXTRA_UID_FOREGROUND_DURATION = "uf"; @@ -55,8 +56,14 @@ public class ScreenPowerStatsLayout extends PowerStatsLayout { PersistableBundle extras = descriptor.extras; mDisplayCount = extras.getInt(EXTRA_DEVICE_SCREEN_COUNT, 1); mDeviceScreenOnDurationPosition = extras.getInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION); - mDeviceBrightnessDurationPositions = extras.getIntArray( + mDeviceBrightnessDurationPositions = getIntArray(extras, EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS); + if (mDeviceBrightnessDurationPositions == null) { + // We should never put arrays in PowerStats.Descriptor because of the performance of + // .equals + mDeviceBrightnessDurationPositions = extras.getIntArray( + EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS_COMPAT); + } mDeviceScreenDozeDurationPosition = extras.getInt(EXTRA_DEVICE_DOZE_DURATION_POSITION); mDeviceScreenDozePowerPosition = extras.getInt(EXTRA_DEVICE_DOZE_POWER_POSITION); mUidTopActivityTimePosition = extras.getInt(EXTRA_UID_FOREGROUND_DURATION); @@ -67,7 +74,7 @@ public class ScreenPowerStatsLayout extends PowerStatsLayout { super.toExtras(extras); extras.putInt(EXTRA_DEVICE_SCREEN_COUNT, mDisplayCount); extras.putInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION, mDeviceScreenOnDurationPosition); - extras.putIntArray(EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS, + putIntArray(extras, EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS, mDeviceBrightnessDurationPositions); extras.putInt(EXTRA_DEVICE_DOZE_DURATION_POSITION, mDeviceScreenDozeDurationPosition); extras.putInt(EXTRA_DEVICE_DOZE_POWER_POSITION, mDeviceScreenDozePowerPosition); diff --git a/services/core/java/com/android/server/power/stats/format/SensorPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/SensorPowerStatsLayout.java index e8df3ddfe5e8..c382534ac433 100644 --- a/services/core/java/com/android/server/power/stats/format/SensorPowerStatsLayout.java +++ b/services/core/java/com/android/server/power/stats/format/SensorPowerStatsLayout.java @@ -27,8 +27,10 @@ import java.util.Map; public class SensorPowerStatsLayout extends PowerStatsLayout { private static final String TAG = "SensorPowerStatsLayout"; - private static final String EXTRA_DEVICE_SENSOR_HANDLES = "dsh"; + private static final String EXTRA_DEVICE_SENSOR_HANDLES = "dshs"; + private static final String EXTRA_DEVICE_SENSOR_HANDLES_COMPAT = "dsh"; private static final String EXTRA_UID_SENSOR_POSITIONS = "usp"; + private static final String EXTRA_UID_SENSOR_POSITIONS_COMPAT = "usps"; private final SparseIntArray mSensorPositions = new SparseIntArray(); @@ -47,8 +49,14 @@ public class SensorPowerStatsLayout extends PowerStatsLayout { super(descriptor); PersistableBundle extras = descriptor.extras; - int[] handlers = extras.getIntArray(EXTRA_DEVICE_SENSOR_HANDLES); - int[] uidDurationPositions = extras.getIntArray(EXTRA_UID_SENSOR_POSITIONS); + int[] handlers = getIntArray(extras, EXTRA_DEVICE_SENSOR_HANDLES); + if (handlers == null) { + handlers = extras.getIntArray(EXTRA_DEVICE_SENSOR_HANDLES_COMPAT); + } + int[] uidDurationPositions = getIntArray(extras, EXTRA_UID_SENSOR_POSITIONS); + if (uidDurationPositions == null) { + uidDurationPositions = extras.getIntArray(EXTRA_UID_SENSOR_POSITIONS_COMPAT); + } if (handlers != null && uidDurationPositions != null) { for (int i = 0; i < handlers.length; i++) { @@ -69,8 +77,8 @@ public class SensorPowerStatsLayout extends PowerStatsLayout { uidDurationPositions[i] = mSensorPositions.valueAt(i); } - extras.putIntArray(EXTRA_DEVICE_SENSOR_HANDLES, handlers); - extras.putIntArray(EXTRA_UID_SENSOR_POSITIONS, uidDurationPositions); + putIntArray(extras, EXTRA_DEVICE_SENSOR_HANDLES, handlers); + putIntArray(extras, EXTRA_UID_SENSOR_POSITIONS, uidDurationPositions); } private void addUidSensorSection(int handle, String label) { diff --git a/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java index a783d543559f..53894a196d24 100644 --- a/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java @@ -25,6 +25,7 @@ import android.os.PersistableBundle; import android.os.UserHandle; import android.text.format.DateFormat; import android.util.IndentingPrintWriter; +import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -347,7 +348,10 @@ class AggregatedPowerStats { Set<Integer> uids = new HashSet<>(); for (int i = 0; i < mPowerComponentStats.size(); i++) { - mPowerComponentStats.valueAt(i).collectUids(uids); + IntArray activeUids = mPowerComponentStats.valueAt(i).getActiveUids(); + for (int j = activeUids.size() - 1; j >= 0; j--) { + uids.add(activeUids.get(j)); + } } Integer[] allUids = uids.toArray(new Integer[uids.size()]); diff --git a/services/core/java/com/android/server/power/stats/processor/BasePowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/BasePowerStatsProcessor.java index d24ea83540cb..1d359335c6d8 100644 --- a/services/core/java/com/android/server/power/stats/processor/BasePowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/BasePowerStatsProcessor.java @@ -24,13 +24,12 @@ import static com.android.server.power.stats.processor.AggregatedPowerStatsConfi import android.os.BatteryConsumer; import android.os.PersistableBundle; +import android.util.IntArray; import com.android.internal.os.PowerStats; import com.android.server.power.stats.format.BasePowerStatsLayout; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; import java.util.function.DoubleSupplier; class BasePowerStatsProcessor extends PowerStatsProcessor { @@ -125,11 +124,12 @@ class BasePowerStatsProcessor extends PowerStatsProcessor { mCumulativeDischargeUah = 0; mCumulativeDischargeDurationMs = 0; - List<Integer> uids = new ArrayList<>(); - stats.collectUids(uids); - - long durationMs = timestampMs - mStartTimestamp; - if (!uids.isEmpty()) { + // Note that we are calling `getUids` rather than `getActiveUids`, because this Processor + // deals with duration rather than power estimation, so it needs to process *all* known + // UIDs, not just the ones that contributed PowerStats + IntArray uids = stats.getUids(); + if (uids.size() != 0) { + long durationMs = timestampMs - mStartTimestamp; for (int i = uids.size() - 1; i >= 0; i--) { long[] uidStats = new long[sStatsLayout.getUidStatsArrayLength()]; sStatsLayout.setUidUsageDuration(uidStats, durationMs); diff --git a/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java index 9fe7f3e7a542..c89dddf45609 100644 --- a/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.os.BatteryStats; import android.os.PersistableBundle; import android.os.Process; +import android.util.IntArray; import com.android.internal.os.PowerStats; import com.android.server.power.stats.UsageBasedPowerEstimator; @@ -27,7 +28,6 @@ import com.android.server.power.stats.format.BinaryStatePowerStatsLayout; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -190,13 +190,13 @@ abstract class BinaryStatePowerStatsProcessor extends PowerStatsProcessor { } computeDevicePowerEstimates(stats, mPlan, mEnergyConsumerSupported); - combineDevicePowerEstimates(stats); - List<Integer> uids = new ArrayList<>(); - stats.collectUids(uids); - - computeUidActivityTotals(stats, uids); - computeUidPowerEstimates(stats, uids); + IntArray uids = stats.getActiveUids(); + if (uids.size() != 0) { + combineDevicePowerEstimates(stats); + computeUidActivityTotals(stats, uids); + computeUidPowerEstimates(stats, uids); + } } protected void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats, @@ -239,8 +239,7 @@ abstract class BinaryStatePowerStatsProcessor extends PowerStatsProcessor { } } - private void computeUidActivityTotals(PowerComponentAggregatedPowerStats stats, - List<Integer> uids) { + private void computeUidActivityTotals(PowerComponentAggregatedPowerStats stats, IntArray uids) { for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) { UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i); Intermediates intermediates = @@ -259,8 +258,7 @@ abstract class BinaryStatePowerStatsProcessor extends PowerStatsProcessor { } } - private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats, - List<Integer> uids) { + private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats, IntArray uids) { for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) { UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i); Intermediates intermediates = @@ -276,12 +274,13 @@ abstract class BinaryStatePowerStatsProcessor extends PowerStatsProcessor { int uid = uids.get(k); if (stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) { - double power = intermediates.power - * mStatsLayout.getUidUsageDuration(mTmpUidStatsArray) - / intermediates.duration; - mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); - stats.setUidStats(uid, proportionalEstimate.stateValues, - mTmpUidStatsArray); + long duration = mStatsLayout.getUidUsageDuration(mTmpUidStatsArray); + if (duration != 0) { + double power = intermediates.power * duration / intermediates.duration; + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, + mTmpUidStatsArray); + } } } } diff --git a/services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java index 4c1a0db02273..c1cd3acf1656 100644 --- a/services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java @@ -16,12 +16,13 @@ package com.android.server.power.stats.processor; +import android.util.IntArray; + import com.android.internal.os.PowerProfile; import com.android.internal.os.PowerStats; import com.android.server.power.stats.UsageBasedPowerEstimator; import com.android.server.power.stats.format.BluetoothPowerStatsLayout; -import java.util.ArrayList; import java.util.List; class BluetoothPowerStatsProcessor extends PowerStatsProcessor { @@ -118,18 +119,19 @@ class BluetoothPowerStatsProcessor extends PowerStatsProcessor { combineDeviceStateEstimates(); - ArrayList<Integer> uids = new ArrayList<>(); - stats.collectUids(uids); - if (!uids.isEmpty()) { - for (int uid : uids) { - for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) { - computeUidActivityTotals(stats, uid, mPlan.uidStateEstimates.get(i)); + IntArray uids = stats.getActiveUids(); + if (uids.size() != 0) { + for (int i = uids.size() - 1; i >= 0; i--) { + int uid = uids.get(i); + for (int j = 0; j < mPlan.uidStateEstimates.size(); j++) { + computeUidActivityTotals(stats, uid, mPlan.uidStateEstimates.get(j)); } } - for (int uid : uids) { - for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) { - computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(i)); + for (int i = uids.size() - 1; i >= 0; i--) { + int uid = uids.get(i); + for (int j = 0; j < mPlan.uidStateEstimates.size(); j++) { + computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(j)); } } } @@ -297,8 +299,10 @@ class BluetoothPowerStatsProcessor extends PowerStatsProcessor { / intermediates.txBytes; } } - mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); - stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + if (power != 0) { + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + } } } } diff --git a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java index 17ceca6e3dc1..ab2489c81449 100644 --- a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java @@ -19,6 +19,7 @@ package com.android.server.power.stats.processor; import android.annotation.Nullable; import android.os.BatteryConsumer; import android.util.ArraySet; +import android.util.IntArray; import android.util.Log; import com.android.internal.os.CpuScalingPolicies; @@ -27,7 +28,6 @@ import com.android.internal.os.PowerStats; import com.android.server.power.stats.format.CpuPowerStatsLayout; import com.android.server.power.stats.format.WakelockPowerStatsLayout; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -119,6 +119,8 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength]; mWakelockDescriptor = null; + + initEnergyConsumerToPowerBracketMaps(); } /** @@ -157,9 +159,6 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { if (mPlan == null) { mPlan = new PowerEstimationPlan(stats.getConfig()); - if (mStatsLayout.getEnergyConsumerCount() != 0) { - initEnergyConsumerToPowerBracketMaps(); - } } Intermediates intermediates = new Intermediates(); @@ -189,12 +188,12 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { estimatePowerByDeviceState(stats, intermediates, wakelockStats); combineDeviceStateEstimates(); - ArrayList<Integer> uids = new ArrayList<>(); - stats.collectUids(uids); - if (!uids.isEmpty()) { - for (int uid : uids) { - for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) { - estimateUidPowerConsumption(stats, uid, mPlan.uidStateEstimates.get(i), + IntArray uids = stats.getActiveUids(); + if (uids.size() != 0) { + for (int i = uids.size() - 1; i >= 0; i--) { + int uid = uids.get(i); + for (int j = 0; j < mPlan.uidStateEstimates.size(); j++) { + estimateUidPowerConsumption(stats, uid, mPlan.uidStateEstimates.get(j), wakelockStats); } } @@ -255,6 +254,10 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { */ private void initEnergyConsumerToPowerBracketMaps() { int energyConsumerCount = mStatsLayout.getEnergyConsumerCount(); + if (energyConsumerCount == 0) { + return; + } + int powerBracketCount = mStatsLayout.getCpuPowerBracketCount(); mEnergyConsumerToCombinedEnergyConsumerMap = new int[energyConsumerCount]; @@ -545,8 +548,10 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { power = Math.max(0, power - wakelockPowerEstimate); } - mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); - stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + if (power != 0) { + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + } } } } diff --git a/services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java index 76adc47cc165..76ea7e841106 100644 --- a/services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java @@ -16,10 +16,11 @@ package com.android.server.power.stats.processor; +import android.util.IntArray; + import com.android.internal.os.PowerStats; import com.android.server.power.stats.format.EnergyConsumerPowerStatsLayout; -import java.util.ArrayList; import java.util.List; class CustomEnergyConsumerPowerStatsProcessor extends PowerStatsProcessor { @@ -40,10 +41,8 @@ class CustomEnergyConsumerPowerStatsProcessor extends PowerStatsProcessor { computeDevicePowerEstimates(stats); - List<Integer> uids = new ArrayList<>(); - stats.collectUids(uids); - - if (!uids.isEmpty()) { + IntArray uids = stats.getActiveUids(); + if (uids.size() != 0) { computeUidPowerEstimates(stats, uids); } } @@ -62,7 +61,7 @@ class CustomEnergyConsumerPowerStatsProcessor extends PowerStatsProcessor { } private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats, - List<Integer> uids) { + IntArray uids) { for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) { UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i); List<UidStateProportionalEstimate> proportionalEstimates = @@ -73,9 +72,12 @@ class CustomEnergyConsumerPowerStatsProcessor extends PowerStatsProcessor { int uid = uids.get(k); if (stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) { - sLayout.setUidPowerEstimate(mTmpUidStatsArray, - uCtoMah(sLayout.getUidConsumedEnergy(mTmpUidStatsArray, 0))); - stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + double power = uCtoMah(sLayout.getUidConsumedEnergy(mTmpUidStatsArray, 0)); + if (power != 0) { + sLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, + mTmpUidStatsArray); + } } } } diff --git a/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java index b4c40de862b4..a544daad82f1 100644 --- a/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java @@ -19,6 +19,7 @@ import android.os.BatteryStats; import android.telephony.CellSignalStrength; import android.telephony.ModemActivityInfo; import android.telephony.ServiceState; +import android.util.IntArray; import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -29,7 +30,6 @@ import com.android.internal.power.ModemPowerProfile; import com.android.server.power.stats.UsageBasedPowerEstimator; import com.android.server.power.stats.format.MobileRadioPowerStatsLayout; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -198,18 +198,19 @@ class MobileRadioPowerStatsProcessor extends PowerStatsProcessor { combineDeviceStateEstimates(); - ArrayList<Integer> uids = new ArrayList<>(); - stats.collectUids(uids); - if (!uids.isEmpty()) { - for (int uid : uids) { - for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) { - computeUidRxTxTotals(stats, uid, mPlan.uidStateEstimates.get(i)); + IntArray uids = stats.getActiveUids(); + if (uids.size() != 0) { + for (int i = uids.size() - 1; i >= 0; i--) { + int uid = uids.get(i); + for (int j = 0; j < mPlan.uidStateEstimates.size(); j++) { + computeUidRxTxTotals(stats, uid, mPlan.uidStateEstimates.get(j)); } } - for (int uid : uids) { - for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) { - computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(i)); + for (int i = uids.size() - 1; i >= 0; i--) { + int uid = uids.get(i); + for (int j = 0; j < mPlan.uidStateEstimates.size(); j++) { + computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(j)); } } } @@ -382,8 +383,10 @@ class MobileRadioPowerStatsProcessor extends PowerStatsProcessor { / intermediates.txPackets; } - mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); - stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + if (power != 0) { + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + } if (DEBUG) { Slog.d(TAG, "UID: " + uid diff --git a/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java b/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java index 28474a554b38..69325757c79d 100644 --- a/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java +++ b/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java @@ -16,6 +16,7 @@ package com.android.server.power.stats.processor; +import android.annotation.Nullable; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -333,9 +334,9 @@ class MultiStateStats { /** * Adds the delta to the metrics. The number of values must correspond to the dimension count - * supplied to the Factory constructor + * supplied to the Factory constructor. Null values is equivalent to an array of zeros. */ - void increment(long[] values, long timestampMs) { + void increment(@Nullable long[] values, long timestampMs) { mCounter.incrementValues(values, timestampMs); mTracking = true; } diff --git a/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java index d4f8fd92fc6c..f9b9da9171cb 100644 --- a/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.os.BatteryStats; import android.os.UserHandle; import android.util.IndentingPrintWriter; +import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; @@ -34,7 +35,6 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.StringWriter; import java.util.Arrays; -import java.util.Collection; import java.util.function.IntConsumer; /** @@ -72,11 +72,11 @@ class PowerComponentAggregatedPowerStats { private MultiStateStats mDeviceStats; private final SparseArray<MultiStateStats> mStateStats = new SparseArray<>(); private final SparseArray<UidStats> mUidStats = new SparseArray<>(); - private long[] mZeroArray; private static class UidStats { public int[] states; public MultiStateStats stats; + public boolean hasPowerStats; public boolean updated; } @@ -200,6 +200,7 @@ class PowerComponentAggregatedPowerStats { if (uidStats.stats == null) { createUidStats(uidStats, mPowerStatsTimestamp); } + uidStats.hasPowerStats = true; uidStats.stats.setStats(states, values); } @@ -240,6 +241,7 @@ class PowerComponentAggregatedPowerStats { } uidStats.stats.increment(powerStats.uidStats.valueAt(i), timestampMs); uidStats.updated = true; + uidStats.hasPowerStats = true; } // For UIDs not mentioned in the PowerStats object, we must assume a 0 increment. @@ -248,11 +250,8 @@ class PowerComponentAggregatedPowerStats { for (int i = mUidStats.size() - 1; i >= 0; i--) { PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i); if (!uidStats.updated && uidStats.stats != null) { - if (mZeroArray == null - || mZeroArray.length != mPowerStatsDescriptor.uidStatsArrayLength) { - mZeroArray = new long[mPowerStatsDescriptor.uidStatsArrayLength]; - } - uidStats.stats.increment(mZeroArray, timestampMs); + // Null stands for an array of zeros + uidStats.stats.increment(null, timestampMs); } uidStats.updated = false; } @@ -267,6 +266,7 @@ class PowerComponentAggregatedPowerStats { mStateStats.clear(); for (int i = mUidStats.size() - 1; i >= 0; i--) { mUidStats.valueAt(i).stats = null; + mUidStats.valueAt(i).hasPowerStats = false; } } @@ -290,12 +290,26 @@ class PowerComponentAggregatedPowerStats { return uidStats; } - void collectUids(Collection<Integer> uids) { + IntArray getUids() { + IntArray uids = new IntArray(mUidStats.size()); + for (int i = mUidStats.size() - 1; i >= 0; i--) { + UidStats uidStats = mUidStats.valueAt(i); + if (uidStats.stats != null) { + uids.add(mUidStats.keyAt(i)); + } + } + return uids; + } + + IntArray getActiveUids() { + IntArray uids = new IntArray(mUidStats.size()); for (int i = mUidStats.size() - 1; i >= 0; i--) { - if (mUidStats.valueAt(i).stats != null) { + UidStats uidStats = mUidStats.valueAt(i); + if (uidStats.hasPowerStats) { uids.add(mUidStats.keyAt(i)); } } + return uids; } boolean getDeviceStats(long[] outValues, int[] deviceStates) { @@ -516,6 +530,7 @@ class PowerComponentAggregatedPowerStats { if (uidStats.stats == null) { createUidStats(uidStats, UNKNOWN); } + uidStats.hasPowerStats = true; if (!uidStats.stats.readFromXml(parser)) { return false; } diff --git a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java index 177d12988a27..634415ece806 100644 --- a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java +++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java @@ -21,6 +21,7 @@ import android.os.AggregateBatteryConsumer; import android.os.BatteryConsumer; import android.os.BatteryUsageStats; import android.os.UidBatteryConsumer; +import android.util.IntArray; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -31,7 +32,6 @@ import com.android.server.power.stats.PowerStatsStore; import com.android.server.power.stats.format.BasePowerStatsLayout; import com.android.server.power.stats.format.PowerStatsLayout; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -180,8 +180,7 @@ class PowerStatsExporter { } } if (layout.isUidPowerAttributionSupported()) { - populateBatteryConsumers(batteryUsageStatsBuilder, - powerComponentStats, layout); + populateBatteryConsumers(batteryUsageStatsBuilder, powerComponentStats, layout); } populateBatteryLevelInfo(batteryUsageStatsBuilder, batteryLevelInfo); @@ -258,6 +257,11 @@ class PowerStatsExporter { BatteryUsageStats.Builder batteryUsageStatsBuilder, PowerComponentAggregatedPowerStats powerComponentStats, PowerStatsLayout layout) { + IntArray uids = powerComponentStats.getUids(); + if (uids.size() == 0) { + return; + } + AggregatedPowerStatsConfig.PowerComponent powerComponent = powerComponentStats.getConfig(); PowerStats.Descriptor descriptor = powerComponentStats.getPowerStatsDescriptor(); long[] uidStats = new long[descriptor.uidStatsArrayLength]; @@ -273,8 +277,6 @@ class PowerStatsExporter { breakDownByProcState = false; } - ArrayList<Integer> uids = new ArrayList<>(); - powerComponentStats.collectUids(uids); for (int screenState = 0; screenState < BatteryConsumer.SCREEN_STATE_COUNT; screenState++) { if (batteryUsageStatsBuilder.isScreenStateDataNeeded()) { if (screenState == BatteryConsumer.SCREEN_STATE_UNSPECIFIED) { @@ -303,7 +305,7 @@ class PowerStatsExporter { private void populateUidBatteryConsumers( BatteryUsageStats.Builder batteryUsageStatsBuilder, PowerComponentAggregatedPowerStats powerComponentStats, PowerStatsLayout layout, - List<Integer> uids, AggregatedPowerStatsConfig.PowerComponent powerComponent, + IntArray uids, AggregatedPowerStatsConfig.PowerComponent powerComponent, long[] uidStats, boolean breakDownByProcState, @BatteryConsumer.ScreenState int screenState, @BatteryConsumer.PowerState int powerState) { @@ -319,7 +321,8 @@ class PowerStatsExporter { long[] durationByProcState = new long[breakDownByProcState ? BatteryConsumer.PROCESS_STATE_COUNT : 1]; double powerAllApps = 0; - for (int uid : uids) { + for (int i = uids.size() - 1; i >= 0; i--) { + int uid = uids.get(i); UidBatteryConsumer.Builder builder = batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid); diff --git a/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java index 8e7498f38fcb..9df3d7eea27b 100644 --- a/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java @@ -27,6 +27,7 @@ import static com.android.server.power.stats.processor.AggregatedPowerStatsConfi import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN; import android.os.BatteryStats; +import android.util.IntArray; import android.util.Slog; import com.android.internal.os.PowerProfile; @@ -34,7 +35,6 @@ import com.android.internal.os.PowerStats; import com.android.server.power.stats.UsageBasedPowerEstimator; import com.android.server.power.stats.format.ScreenPowerStatsLayout; -import java.util.ArrayList; import java.util.List; class ScreenPowerStatsProcessor extends PowerStatsProcessor { @@ -116,10 +116,8 @@ class ScreenPowerStatsProcessor extends PowerStatsProcessor { computeDevicePowerEstimates(stats); combineDeviceStateEstimates(); - List<Integer> uids = new ArrayList<>(); - stats.collectUids(uids); - - if (!uids.isEmpty()) { + IntArray uids = stats.getActiveUids(); + if (uids.size() != 0) { computeUidPowerEstimates(stats, uids); } mPlan.resetIntermediates(); @@ -197,7 +195,7 @@ class ScreenPowerStatsProcessor extends PowerStatsProcessor { } private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats, - List<Integer> uids) { + IntArray uids) { int[] uidStateValues = new int[stats.getConfig().getUidStateConfig().length]; uidStateValues[STATE_SCREEN] = SCREEN_STATE_ON; uidStateValues[STATE_PROCESS_STATE] = PROCESS_STATE_UNSPECIFIED; @@ -232,9 +230,11 @@ class ScreenPowerStatsProcessor extends PowerStatsProcessor { int uid = uids.get(j); if (stats.getUidStats(mTmpUidStatsArray, uid, uidStateValues)) { long duration = mStatsLayout.getUidTopActivityDuration(mTmpUidStatsArray); - double power = intermediates.power * duration / totalTopActivityDuration; - mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); - stats.setUidStats(uid, uidStateValues, mTmpUidStatsArray); + if (duration != 0) { + double power = intermediates.power * duration / totalTopActivityDuration; + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, uidStateValues, mTmpUidStatsArray); + } } } } diff --git a/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java index 0bb028bce5af..ba728d36d561 100644 --- a/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java @@ -21,6 +21,7 @@ import android.hardware.SensorManager; import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.PersistableBundle; +import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; @@ -28,7 +29,6 @@ import com.android.internal.os.PowerStats; import com.android.server.power.stats.format.PowerStatsLayout; import com.android.server.power.stats.format.SensorPowerStatsLayout; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Supplier; @@ -207,11 +207,11 @@ class SensorPowerStatsProcessor extends PowerStatsProcessor { mPlan = new PowerEstimationPlan(stats.getConfig()); } - List<Integer> uids = new ArrayList<>(); - stats.collectUids(uids); - - computeUidPowerEstimates(stats, uids); - computeDevicePowerEstimates(stats); + IntArray uids = stats.getActiveUids(); + if (uids.size() != 0) { + computeUidPowerEstimates(stats, uids); + computeDevicePowerEstimates(stats); + } mPlan.resetIntermediates(); } @@ -239,9 +239,7 @@ class SensorPowerStatsProcessor extends PowerStatsProcessor { mLastUpdateTimestamp = timestamp; } - private void computeUidPowerEstimates( - PowerComponentAggregatedPowerStats stats, - List<Integer> uids) { + private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats, IntArray uids) { List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL); int[] uidSensorDurationPositions = new int[sensorList.size()]; double[] sensorPower = new double[sensorList.size()]; @@ -292,8 +290,7 @@ class SensorPowerStatsProcessor extends PowerStatsProcessor { } } - private void computeDevicePowerEstimates( - PowerComponentAggregatedPowerStats stats) { + private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) { for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) { CombinedDeviceStateEstimate estimation = mPlan.combinedDeviceStateEstimations.get(i); diff --git a/services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java index 0df01cf7e5d1..8cc0b6eb6150 100644 --- a/services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java @@ -16,6 +16,7 @@ package com.android.server.power.stats.processor; +import android.util.IntArray; import android.util.Slog; import com.android.internal.os.PowerProfile; @@ -23,7 +24,6 @@ import com.android.internal.os.PowerStats; import com.android.server.power.stats.UsageBasedPowerEstimator; import com.android.server.power.stats.format.WifiPowerStatsLayout; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -148,18 +148,19 @@ class WifiPowerStatsProcessor extends PowerStatsProcessor { combineDeviceStateEstimates(); - ArrayList<Integer> uids = new ArrayList<>(); - stats.collectUids(uids); - if (!uids.isEmpty()) { - for (int uid : uids) { - for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) { - computeUidActivityTotals(stats, uid, mPlan.uidStateEstimates.get(i)); + IntArray uids = stats.getActiveUids(); + if (uids.size() != 0) { + for (int i = uids.size() - 1; i >= 0; i--) { + int uid = uids.get(i); + for (int j = 0; j < mPlan.uidStateEstimates.size(); j++) { + computeUidActivityTotals(stats, uid, mPlan.uidStateEstimates.get(j)); } } - for (int uid : uids) { - for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) { - computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(i)); + for (int i = uids.size() - 1; i >= 0; i--) { + int uid = uids.get(i); + for (int j = 0; j < mPlan.uidStateEstimates.size(); j++) { + computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(j)); } } } @@ -374,8 +375,10 @@ class WifiPowerStatsProcessor extends PowerStatsProcessor { / intermediates.batchedScanDuration; } } - mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); - stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + if (power != 0) { + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + } if (DEBUG) { Slog.d(TAG, "UID: " + uid diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java index a75d110e3cd1..17739712d65a 100644 --- a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java +++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java @@ -88,6 +88,5 @@ public class ResourcesManagerShellCommand extends ShellCommand { out.println(" Print this help text."); out.println(" dump <PROCESS>"); out.println(" Dump the Resources objects in use as well as the history of Resources"); - } } diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index ab756f2a755b..5347ca46ab31 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -29,7 +29,6 @@ import android.annotation.WorkerThread; import android.content.Context; import android.content.Intent; import android.content.IntentSender; -import android.content.pm.Flags; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -965,9 +964,7 @@ class Rollback { ipw.println("-stateDescription: " + mStateDescription); ipw.println("-timestamp: " + getTimestamp()); ipw.println("-rollbackLifetimeMillis: " + getRollbackLifetimeMillis()); - if (Flags.recoverabilityDetection()) { - ipw.println("-rollbackImpactLevel: " + info.getRollbackImpactLevel()); - } + ipw.println("-rollbackImpactLevel: " + info.getRollbackImpactLevel()); ipw.println("-isStaged: " + isStaged()); ipw.println("-originalSessionId: " + getOriginalSessionId()); ipw.println("-packages:"); diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 2e6be5bb56a8..9ed52d8b3504 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -1244,17 +1244,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba rollback.makeAvailable(); mPackageHealthObserver.notifyRollbackAvailable(rollback.info); - if (Flags.recoverabilityDetection()) { - if (rollback.info.getRollbackImpactLevel() == PackageManager.ROLLBACK_USER_IMPACT_LOW) { - // TODO(zezeozue): Provide API to explicitly start observing instead - // of doing this for all rollbacks. If we do this for all rollbacks, - // should document in PackageInstaller.SessionParams#setEnableRollback - // After enabling and committing any rollback, observe packages and - // prepare to rollback if packages crashes too frequently. - mPackageWatchdog.startExplicitHealthCheck(rollback.getPackageNames(), - mRollbackLifetimeDurationInMillis, mPackageHealthObserver); - } - } else { + if (rollback.info.getRollbackImpactLevel() == PackageManager.ROLLBACK_USER_IMPACT_LOW) { + // TODO(zezeozue): Provide API to explicitly start observing instead + // of doing this for all rollbacks. If we do this for all rollbacks, + // should document in PackageInstaller.SessionParams#setEnableRollback + // After enabling and committing any rollback, observe packages and + // prepare to rollback if packages crashes too frequently. mPackageWatchdog.startExplicitHealthCheck(rollback.getPackageNames(), mRollbackLifetimeDurationInMillis, mPackageHealthObserver); } diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index 50db1e4ac30e..6dc40323f2ee 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -197,9 +197,7 @@ class RollbackStore { json.put("isStaged", rollback.isStaged()); json.put("causePackages", versionedPackagesToJson(rollback.getCausePackages())); json.put("committedSessionId", rollback.getCommittedSessionId()); - if (Flags.recoverabilityDetection()) { - json.put("rollbackImpactLevel", rollback.getRollbackImpactLevel()); - } + json.put("rollbackImpactLevel", rollback.getRollbackImpactLevel()); return json; } @@ -211,11 +209,9 @@ class RollbackStore { versionedPackagesFromJson(json.getJSONArray("causePackages")), json.getInt("committedSessionId")); - if (Flags.recoverabilityDetection()) { - // to make it backward compatible. - rollbackInfo.setRollbackImpactLevel(json.optInt("rollbackImpactLevel", - PackageManager.ROLLBACK_USER_IMPACT_LOW)); - } + // to make it backward compatible. + rollbackInfo.setRollbackImpactLevel(json.optInt("rollbackImpactLevel", + PackageManager.ROLLBACK_USER_IMPACT_LOW)); return rollbackInfo; } diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java index f060e4d11e82..82df310db9a4 100644 --- a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java +++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java @@ -303,7 +303,11 @@ class AttestationVerificationPeerDeviceVerifier { if (mRevocationEnabled) { // Checks Revocation Status List based on // https://developer.android.com/training/articles/security-key-attestation#certificate_status - mCertificateRevocationStatusManager.checkRevocationStatus(certificates); + // The first certificate is the leaf, which is generated at runtime with the attestation + // attributes such as the challenge. It is specific to this attestation instance and + // does not need to be checked for revocation. + mCertificateRevocationStatusManager.checkRevocationStatus( + new ArrayList<>(certificates.subList(1, certificates.size()))); } } diff --git a/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java b/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java index d36d9f5f6636..4cd4b3b84910 100644 --- a/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java +++ b/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java @@ -42,6 +42,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.security.cert.CertPathValidatorException; import java.security.cert.X509Certificate; +import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; @@ -67,6 +68,8 @@ class CertificateRevocationStatusManager { */ @VisibleForTesting static final int MAX_DAYS_SINCE_LAST_CHECK = 30; + @VisibleForTesting static final int NUM_HOURS_BEFORE_NEXT_CHECK = 24; + /** * The number of days since issue date for an intermediary certificate to be considered fresh * and not require a revocation list check. @@ -127,6 +130,17 @@ class CertificateRevocationStatusManager { serialNumbers.add(serialNumber); } try { + if (isLastCheckedWithin(Duration.ofHours(NUM_HOURS_BEFORE_NEXT_CHECK), serialNumbers)) { + Slog.d( + TAG, + "All certificates have been checked for revocation recently. No need to" + + " check this time."); + return; + } + } catch (IOException ignored) { + // Proceed to check the revocation status + } + try { JSONObject revocationList = fetchRemoteRevocationList(); Map<String, Boolean> areCertificatesRevoked = new HashMap<>(); for (String serialNumber : serialNumbers) { @@ -151,25 +165,32 @@ class CertificateRevocationStatusManager { serialNumbers.remove(serialNumber); } } - Map<String, LocalDateTime> lastRevocationCheckData; try { - lastRevocationCheckData = getLastRevocationCheckData(); + if (!isLastCheckedWithin( + Duration.ofDays(MAX_DAYS_SINCE_LAST_CHECK), serialNumbers)) { + throw new CertPathValidatorException( + "Unable to verify the revocation status of one of the certificates " + + serialNumbers); + } } catch (IOException ex2) { throw new CertPathValidatorException( "Unable to load stored revocation status", ex2); } - for (String serialNumber : serialNumbers) { - if (!lastRevocationCheckData.containsKey(serialNumber) - || lastRevocationCheckData - .get(serialNumber) - .isBefore( - LocalDateTime.now().minusDays(MAX_DAYS_SINCE_LAST_CHECK))) { - throw new CertPathValidatorException( - "Unable to verify the revocation status of certificate " - + serialNumber); - } + } + } + + private boolean isLastCheckedWithin(Duration lastCheckedWithin, List<String> serialNumbers) + throws IOException { + Map<String, LocalDateTime> lastRevocationCheckData = getLastRevocationCheckData(); + for (String serialNumber : serialNumbers) { + if (!lastRevocationCheckData.containsKey(serialNumber) + || lastRevocationCheckData + .get(serialNumber) + .isBefore(LocalDateTime.now().minus(lastCheckedWithin))) { + return false; } } + return true; } private static boolean needToCheckRevocationStatus( diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java index bfd86d724583..9f9a9807d973 100644 --- a/services/core/java/com/android/server/security/FileIntegrityService.java +++ b/services/core/java/com/android/server/security/FileIntegrityService.java @@ -54,11 +54,6 @@ public class FileIntegrityService extends SystemService { super(PermissionEnforcer.fromContext(context)); } - @Override - public boolean isApkVeritySupported() { - return VerityUtils.isFsVeritySupported(); - } - private void checkCallerPackageName(String packageName) { final int callingUid = Binder.getCallingUid(); final int callingUserId = UserHandle.getUserId(callingUid); diff --git a/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java index 687442b47fb3..cdeacaa2e43a 100644 --- a/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java +++ b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java @@ -62,7 +62,7 @@ public class DataAggregator { /** Initialize DataSources */ private void initialize() { mDataSources.add(new SecurityLogSource(mContext, this)); - mDataSources.add(new NetworkLogSource(mContext, this)); + mDataSources.add(new NetworkLogSource(this)); } /** diff --git a/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java index f303a588d30c..fe0cf80a48f2 100644 --- a/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java +++ b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java @@ -18,7 +18,6 @@ package com.android.server.security.intrusiondetection; import android.app.admin.ConnectEvent; import android.app.admin.DnsEvent; -import android.content.Context; import android.content.pm.PackageManagerInternal; import android.net.IIpConnectivityMetrics; import android.net.INetdEventCallback; @@ -44,8 +43,7 @@ public class NetworkLogSource implements DataSource { private IIpConnectivityMetrics mIpConnectivityMetrics; private long mId; - public NetworkLogSource(Context context, DataAggregator dataAggregator) - throws SecurityException { + public NetworkLogSource(DataAggregator dataAggregator) throws SecurityException { mDataAggregator = dataAggregator; mPm = LocalServices.getService(PackageManagerInternal.class); mId = 0; diff --git a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java index 142094c9d9f4..7501799198e8 100644 --- a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java +++ b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java @@ -19,14 +19,15 @@ package com.android.server.security.intrusiondetection; import android.Manifest.permission; import android.annotation.RequiresPermission; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.SecurityLog.SecurityEvent; import android.content.Context; import android.security.intrusiondetection.IntrusionDetectionEvent; import android.util.Slog; +import com.android.server.LocalServices; + import java.util.List; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -36,13 +37,13 @@ public class SecurityLogSource implements DataSource { private SecurityEventCallback mEventCallback; private DevicePolicyManager mDpm; - private Executor mExecutor; + private DevicePolicyManagerInternal mDpmInternal; private DataAggregator mDataAggregator; public SecurityLogSource(Context context, DataAggregator dataAggregator) { mDataAggregator = dataAggregator; mDpm = context.getSystemService(DevicePolicyManager.class); - mExecutor = Executors.newSingleThreadExecutor(); + mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class); mEventCallback = new SecurityEventCallback(); } @@ -50,12 +51,13 @@ public class SecurityLogSource implements DataSource { @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void enable() { enableAuditLog(); - mDpm.setAuditLogEventCallback(mExecutor, mEventCallback); + mDpmInternal.setInternalEventsCallback(mEventCallback); } @Override @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void disable() { + mDpmInternal.setInternalEventsCallback(null); disableAuditLog(); } @@ -82,10 +84,11 @@ public class SecurityLogSource implements DataSource { @Override public void accept(List<SecurityEvent> events) { - if (events.size() == 0) { + if (events == null || events.size() == 0) { Slog.w(TAG, "No events received; caller may not be authorized"); return; } + List<IntrusionDetectionEvent> intrusionDetectionEvents = events.stream() .filter(event -> event != null) diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java index 1b6ce9dacfa9..64b52b175252 100644 --- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java +++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java @@ -176,14 +176,14 @@ final class VendorVibrationSession extends IVibrationSession.Stub @Override public void onCancel() { - Slog.d(TAG, "Cancellation signal received, cancelling vibration session..."); + Slog.d(TAG, "Session cancellation signal received, aborting vibration session..."); requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true, /* isVendorRequest= */ true); } @Override public void binderDied() { - Slog.d(TAG, "Binder died, cancelling vibration session..."); + Slog.d(TAG, "Session binder died, aborting vibration session..."); requestEndSession(Status.CANCELLED_BINDER_DIED, /* shouldAbort= */ true, /* isVendorRequest= */ false); } @@ -219,18 +219,20 @@ final class VendorVibrationSession extends IVibrationSession.Stub @Override public void notifyVibratorCallback(int vibratorId, long vibrationId) { - // Ignore it, the session vibration playback doesn't depend on HAL timings + Slog.d(TAG, "Vibration callback received for vibration " + vibrationId + + " on vibrator " + vibratorId + ", ignoring..."); } @Override public void notifySyncedVibratorsCallback(long vibrationId) { - // Ignore it, the session vibration playback doesn't depend on HAL timings + Slog.d(TAG, "Synced vibration callback received for vibration " + vibrationId + + ", ignoring..."); } @Override public void notifySessionCallback() { + Slog.d(TAG, "Session callback received, ending vibration session..."); synchronized (mLock) { - Slog.d(TAG, "Session callback received, ending vibration session..."); // If end was not requested then the HAL has cancelled the session. maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON, /* isVendorRequest= */ false); @@ -307,7 +309,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub } } if (isAlreadyEnded) { - // Session already ended, make sure we end it in the HAL. + Slog.d(TAG, "Session already ended after starting the HAL, aborting..."); mHandler.post(() -> mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true)); } } @@ -335,8 +337,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub public boolean maybeSetVibrationConductor(VibrationStepConductor conductor) { synchronized (mLock) { if (mConductor != null) { - Slog.d(TAG, "Vibration session still dispatching previous vibration," - + " new vibration ignored"); + Slog.d(TAG, "Session still dispatching previous vibration, new vibration " + + conductor.getVibration().id + " ignored"); return false; } mConductor = conductor; @@ -345,19 +347,22 @@ final class VendorVibrationSession extends IVibrationSession.Stub } private void requestEndSession(Status status, boolean shouldAbort, boolean isVendorRequest) { + Slog.d(TAG, "Session end request received with status " + status); boolean shouldTriggerSessionHook = false; synchronized (mLock) { maybeSetEndRequestLocked(status, isVendorRequest); - if (isStarted()) { - // Always trigger session hook after it has started, in case new request aborts an - // already finishing session. Wait for HAL callback before actually ending here. + if (!isEnded() && isStarted()) { + // Trigger session hook even if it was already triggered, in case a second request + // is aborting the ongoing/ending session. This might cause it to end right away. + // Wait for HAL callback before setting the end status. shouldTriggerSessionHook = true; } else { - // Session did not start in the HAL, end it right away. + // Session not active in the HAL, set end status right away. maybeSetStatusToRequestedLocked(); } } if (shouldTriggerSessionHook) { + Slog.d(TAG, "Requesting HAL session end with abort=" + shouldAbort); mHandler.post(() -> mManagerHooks.endSession(mSessionId, shouldAbort)); } } @@ -368,6 +373,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub // End already requested, keep first requested status and time. return; } + Slog.d(TAG, "Session end request accepted for status " + status); mEndStatusRequest = status; mEndedByVendor = isVendorRequest; mEndTime = System.currentTimeMillis(); @@ -400,6 +406,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub // No end status was requested, nothing to set. return; } + Slog.d(TAG, "Session end request applied for status " + mEndStatusRequest); mStatus = mEndStatusRequest; // Run client callback in separate thread. final Status endStatus = mStatus; @@ -407,7 +414,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub try { mCallback.onFinished(toSessionStatus(endStatus)); } catch (RemoteException e) { - Slog.e(TAG, "Error notifying vendor session is finishing", e); + Slog.e(TAG, "Error notifying vendor session finished", e); } }); } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 804cf4663bfd..75b1b202bcfd 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -61,6 +61,7 @@ import android.os.VibratorInfo; import android.os.vibrator.Flags; import android.os.vibrator.IVibrationSessionCallback; import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; import android.os.vibrator.VibrationConfig; import android.os.vibrator.VibrationEffectSegment; import android.os.vibrator.VibratorInfoFactory; @@ -786,7 +787,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { synchronized (mLock) { if (DEBUG) { - Slog.d(TAG, "Starting session " + session.getSessionId()); + Slog.d(TAG, "Starting vendor session " + session.getSessionId()); } Status ignoreStatus = null; @@ -864,13 +865,16 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // Session already ended, possibly cancelled by app cancellation signal. return session.getStatus(); } - int mode = startAppOpModeLocked(session.getCallerInfo()); + CallerInfo callerInfo = session.getCallerInfo(); + int mode = startAppOpModeLocked(callerInfo); switch (mode) { case AppOpsManager.MODE_ALLOWED: Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0); // Make sure mCurrentVibration is set while triggering the HAL. mCurrentSession = session; if (!session.linkToDeath()) { + // Shouldn't happen. The method call already logs. + finishAppOpModeLocked(callerInfo); mCurrentSession = null; return Status.IGNORED_ERROR_TOKEN; } @@ -878,14 +882,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Slog.e(TAG, "Error starting session " + sessionId + " on vibrators " + Arrays.toString(session.getVibratorIds())); session.unlinkToDeath(); + finishAppOpModeLocked(callerInfo); mCurrentSession = null; return Status.IGNORED_UNSUPPORTED; } session.notifyStart(); return null; case AppOpsManager.MODE_ERRORED: - Slog.w(TAG, "Start AppOpsManager operation errored for uid " - + session.getCallerInfo().uid); + Slog.w(TAG, "Start AppOpsManager operation errored for uid " + callerInfo.uid); return Status.IGNORED_ERROR_APP_OPS; default: return Status.IGNORED_APP_OPS; @@ -1081,11 +1085,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @Nullable private Status startVibrationOnThreadLocked(SingleVibrationSession session) { if (DEBUG) { - Slog.d(TAG, "Starting vibration " + session.getVibration().id + " on thread"); + Slog.d(TAG, "Starting vibration " + session.getVibration().id + " on thread"); } VibrationStepConductor conductor = createVibrationStepConductor(session.getVibration()); session.setVibrationConductor(conductor); - int mode = startAppOpModeLocked(session.getCallerInfo()); + CallerInfo callerInfo = session.getCallerInfo(); + int mode = startAppOpModeLocked(callerInfo); switch (mode) { case AppOpsManager.MODE_ALLOWED: Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0); @@ -1093,19 +1098,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mCurrentSession = session; if (!mCurrentSession.linkToDeath()) { // Shouldn't happen. The method call already logs. + finishAppOpModeLocked(callerInfo); mCurrentSession = null; // Aborted. return Status.IGNORED_ERROR_TOKEN; } if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) { // Shouldn't happen. The method call already logs. session.setVibrationConductor(null); // Rejected by thread, clear it in session. + mCurrentSession.unlinkToDeath(); + finishAppOpModeLocked(callerInfo); mCurrentSession = null; // Aborted. return Status.IGNORED_ERROR_SCHEDULING; } return null; case AppOpsManager.MODE_ERRORED: - Slog.w(TAG, "Start AppOpsManager operation errored for uid " - + session.getCallerInfo().uid); + Slog.w(TAG, "Start AppOpsManager operation errored for uid " + callerInfo.uid); return Status.IGNORED_ERROR_APP_OPS; default: return Status.IGNORED_APP_OPS; @@ -1494,6 +1501,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private int checkAppOpModeLocked(CallerInfo callerInfo) { int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE, callerInfo.attrs.getAudioUsage(), callerInfo.uid, callerInfo.opPkg); + if (DEBUG) { + int opMode = mAppOps.checkOpNoThrow(AppOpsManager.OP_VIBRATE, callerInfo.uid, + callerInfo.opPkg); + Slog.d(TAG, "Check AppOp mode VIBRATE for uid " + callerInfo.uid + " and package " + + callerInfo.opPkg + " returned audio=" + AppOpsManager.MODE_NAMES[mode] + + ", op=" + AppOpsManager.MODE_NAMES[opMode]); + } int fixedMode = fixupAppOpModeLocked(mode, callerInfo.attrs); if (mode != fixedMode && fixedMode == AppOpsManager.MODE_ALLOWED) { // If we're just ignoring the vibration op then this is set by DND and we should ignore @@ -1507,9 +1521,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { /** Start an operation in {@link AppOpsManager}, if allowed. */ @GuardedBy("mLock") private int startAppOpModeLocked(CallerInfo callerInfo) { - return fixupAppOpModeLocked( - mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, callerInfo.uid, callerInfo.opPkg), - callerInfo.attrs); + int mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, callerInfo.uid, + callerInfo.opPkg); + if (DEBUG) { + Slog.d(TAG, "Start AppOp mode VIBRATE for uid " + callerInfo.uid + " and package " + + callerInfo.opPkg + " returned " + AppOpsManager.MODE_NAMES[mode]); + } + return fixupAppOpModeLocked(mode, callerInfo.attrs); } /** @@ -1518,6 +1536,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ @GuardedBy("mLock") private void finishAppOpModeLocked(CallerInfo callerInfo) { + if (DEBUG) { + Slog.d(TAG, "Finish AppOp mode VIBRATE for uid " + callerInfo.uid + " and package " + + callerInfo.opPkg); + } mAppOps.finishOp(AppOpsManager.OP_VIBRATE, callerInfo.uid, callerInfo.opPkg); } @@ -2651,7 +2673,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { CombinedVibration.ParallelCombination combination = CombinedVibration.startParallel(); while ("-v".equals(getNextOption())) { - int vibratorId = Integer.parseInt(getNextArgRequired()); + int vibratorId = parseInt(getNextArgRequired(), "Expected vibrator id after -v"); combination.addVibrator(vibratorId, nextEffect()); } runVibrate(commonOptions, combination.combine()); @@ -2663,7 +2685,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { CombinedVibration.SequentialCombination combination = CombinedVibration.startSequential(); while ("-v".equals(getNextOption())) { - int vibratorId = Integer.parseInt(getNextArgRequired()); + int vibratorId = parseInt(getNextArgRequired(), "Expected vibrator id after -v"); combination.addNext(vibratorId, nextEffect()); } runVibrate(commonOptions, combination.combine()); @@ -2688,7 +2710,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private int runHapticFeedback() { CommonOptions commonOptions = new CommonOptions(); - int constant = Integer.parseInt(getNextArgRequired()); + int constant = parseInt(getNextArgRequired(), "Expected haptic feedback constant id"); IBinder deathBinder = commonOptions.background ? VibratorManagerService.this : mShellCallbacksToken; @@ -2715,6 +2737,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { addPrebakedToComposition(composition); } else if ("primitives".equals(nextArg)) { addPrimitivesToComposition(composition); + } else if ("envelope".equals(nextArg)) { + addEnvelopeToComposition(composition); } else { // nextArg is not an effect, finish reading. break; @@ -2734,17 +2758,135 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if ("-a".equals(nextOption)) { hasAmplitude = true; } else if ("-w".equals(nextOption)) { - delay = Integer.parseInt(getNextArgRequired()); + delay = parseInt(getNextArgRequired(), "Expected delay millis after -w"); } } - long duration = Long.parseLong(getNextArgRequired()); - int amplitude = hasAmplitude ? Integer.parseInt(getNextArgRequired()) + long duration = parseInt(getNextArgRequired(), "Expected one-shot duration millis"); + int amplitude = hasAmplitude + ? parseInt(getNextArgRequired(), "Expected one-shot amplitude") : VibrationEffect.DEFAULT_AMPLITUDE; composition.addOffDuration(Duration.ofMillis(delay)); composition.addEffect(VibrationEffect.createOneShot(duration, amplitude)); } + private interface EnvelopeBuilder { + void setInitialSharpness(float sharpness); + void addControlPoint(float intensity, float sharpness, long duration); + void reset(float initialSharpness); + VibrationEffect build(); + } + + private static class BasicEnveloperBuilderWrapper implements EnvelopeBuilder { + private VibrationEffect.BasicEnvelopeBuilder mBuilder = + new VibrationEffect.BasicEnvelopeBuilder(); + + @Override + public void setInitialSharpness(float sharpness) { + mBuilder.setInitialSharpness(sharpness); + } + + @Override + public void addControlPoint(float intensity, float sharpness, long duration) { + mBuilder.addControlPoint(intensity, sharpness, duration); + } + + @Override + public void reset(float initialSharpness) { + mBuilder = new VibrationEffect.BasicEnvelopeBuilder(); + mBuilder.setInitialSharpness(initialSharpness); + } + + @Override + public VibrationEffect build() { + return mBuilder.build(); + } + } + + private static class AdvancedEnveloperBuilderWrapper implements EnvelopeBuilder { + private VibrationEffect.WaveformEnvelopeBuilder mBuilder = + new VibrationEffect.WaveformEnvelopeBuilder(); + + @Override + public void setInitialSharpness(float sharpness) { + mBuilder.setInitialFrequencyHz(sharpness); + } + + @Override + public void addControlPoint(float intensity, float sharpness, long duration) { + mBuilder.addControlPoint(intensity, sharpness, duration); + } + + @Override + public void reset(float initialSharpness) { + mBuilder = new VibrationEffect.WaveformEnvelopeBuilder(); + mBuilder.setInitialFrequencyHz(initialSharpness); + } + + @Override + public VibrationEffect build() { + return mBuilder.build(); + } + } + + private void addEnvelopeToComposition(VibrationEffect.Composition composition) { + getNextArgRequired(); // consume "envelope" + int repeat = -1; + float initialSharpness = Float.NaN; + VibrationEffect preamble = null; + boolean isAdvanced = false; + String nextOption; + while ((nextOption = getNextOption()) != null) { + switch (nextOption) { + case "-a" -> isAdvanced = true; + case "-i" -> initialSharpness = parseFloat(getNextArgRequired(), + "Expected initial sharpness after -i"); + case "-r" -> repeat = parseInt(getNextArgRequired(), + "Expected repeat index after -r"); + } + } + + EnvelopeBuilder builder = isAdvanced ? new AdvancedEnveloperBuilderWrapper() + : new BasicEnveloperBuilderWrapper(); + + if (!Float.isNaN(initialSharpness)) { + builder.setInitialSharpness(initialSharpness); + } + + int duration, pos = 0; + float intensity, sharpness = 0f; + String nextArg; + while ((nextArg = peekNextArg()) != null) { + if (pos > 0 && pos == repeat) { + preamble = builder.build(); + builder.reset(sharpness); + } + try { + duration = Integer.parseInt(nextArg); + getNextArgRequired(); // consume the duration + } catch (NumberFormatException e) { + // nextArg is not a duration, finish reading. + break; + } + intensity = parseFloat(getNextArgRequired(), "Expected envelope intensity"); + sharpness = parseFloat(getNextArgRequired(), "Expected envelope sharpness"); + builder.addControlPoint(intensity, sharpness, duration); + pos++; + } + + if (repeat >= 0) { + if (preamble == null) { + composition.addEffect(VibrationEffect.createRepeatingEffect(builder.build())); + } else { + composition.addEffect( + VibrationEffect.createRepeatingEffect(preamble, builder.build())); + } + return; + } + + composition.addEffect(builder.build()); + } + private void addWaveformToComposition(VibrationEffect.Composition composition) { boolean hasAmplitudes = false; boolean hasFrequencies = false; @@ -2755,16 +2897,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { getNextArgRequired(); // consume "waveform" String nextOption; while ((nextOption = getNextOption()) != null) { - if ("-a".equals(nextOption)) { - hasAmplitudes = true; - } else if ("-r".equals(nextOption)) { - repeat = Integer.parseInt(getNextArgRequired()); - } else if ("-w".equals(nextOption)) { - delay = Integer.parseInt(getNextArgRequired()); - } else if ("-f".equals(nextOption)) { - hasFrequencies = true; - } else if ("-c".equals(nextOption)) { - isContinuous = true; + switch (nextOption) { + case "-a" -> hasAmplitudes = true; + case "-f" -> hasFrequencies = true; + case "-c" -> isContinuous = true; + case "-r" -> repeat = parseInt(getNextArgRequired(), + "Expected repeat index after -r"); + case "-w" -> delay = parseInt(getNextArgRequired(), + "Expected delay millis after -w"); } } List<Integer> durations = new ArrayList<>(); @@ -2782,14 +2922,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { break; } if (hasAmplitudes) { - amplitudes.add( - Float.parseFloat(getNextArgRequired()) / VibrationEffect.MAX_AMPLITUDE); + int amplitude = parseInt(getNextArgRequired(), "Expected waveform amplitude"); + amplitudes.add((float) amplitude / VibrationEffect.MAX_AMPLITUDE); } else { amplitudes.add(nextAmplitude); nextAmplitude = 1 - nextAmplitude; } if (hasFrequencies) { - frequencies.add(Float.parseFloat(getNextArgRequired())); + frequencies.add( + parseFloat(getNextArgRequired(), "Expected waveform frequency")); } } @@ -2848,27 +2989,37 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if ("-b".equals(nextOption)) { shouldFallback = true; } else if ("-w".equals(nextOption)) { - delay = Integer.parseInt(getNextArgRequired()); + delay = parseInt(getNextArgRequired(), "Expected delay millis after -w"); } } - int effectId = Integer.parseInt(getNextArgRequired()); + int effectId = parseInt(getNextArgRequired(), "Expected prebaked effect id"); composition.addOffDuration(Duration.ofMillis(delay)); composition.addEffect(VibrationEffect.get(effectId, shouldFallback)); } private void addPrimitivesToComposition(VibrationEffect.Composition composition) { getNextArgRequired(); // consume "primitives" - String nextArg; - while ((nextArg = peekNextArg()) != null) { + while (peekNextArg() != null) { int delay = 0; - if ("-w".equals(nextArg)) { - getNextArgRequired(); // consume "-w" - delay = Integer.parseInt(getNextArgRequired()); - nextArg = peekNextArg(); + float scale = 1f; + int delayType = PrimitiveSegment.DEFAULT_DELAY_TYPE; + + String nextOption; + while ((nextOption = getNextOption()) != null) { + if ("-s".equals(nextOption)) { + scale = parseFloat(getNextArgRequired(), "Expected scale after -s"); + } else if ("-o".equals(nextOption)) { + delayType = VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET; + delay = parseInt(getNextArgRequired(), "Expected offset millis after -o"); + } else if ("-w".equals(nextOption)) { + delayType = PrimitiveSegment.DEFAULT_DELAY_TYPE; + delay = parseInt(getNextArgRequired(), "Expected delay millis after -w"); + } } try { - composition.addPrimitive(Integer.parseInt(nextArg), /* scale= */ 1, delay); + String nextArg = peekNextArg(); // Just in case this is not a primitive. + composition.addPrimitive(Integer.parseInt(nextArg), scale, delay, delayType); getNextArgRequired(); // consume the primitive id } catch (NumberFormatException | NullPointerException e) { // nextArg is not describing a primitive, leave it to be consumed by outer loops @@ -2894,17 +3045,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { VibrationXmlParser.parseDocument(new StringReader(xml)); VibratorInfo combinedVibratorInfo = getCombinedVibratorInfo(); if (combinedVibratorInfo == null) { - throw new IllegalStateException( - "No combined vibrator info to parse vibration XML " + xml); + throw new IllegalStateException("No vibrator info available to parse XML"); } VibrationEffect effect = parsedVibration.resolve(combinedVibratorInfo); if (effect == null) { - throw new IllegalArgumentException( - "Parsed vibration cannot be resolved for vibration XML " + xml); + throw new IllegalArgumentException("Parsed XML cannot be resolved: " + xml); } return CombinedVibration.createParallel(effect); } catch (IOException e) { - throw new RuntimeException("Error parsing vibration XML " + xml, e); + throw new RuntimeException("Error parsing XML: " + xml, e); } } @@ -2922,16 +3071,30 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + private static int parseInt(String text, String errorMessage) { + try { + return Integer.parseInt(text); + } catch (NumberFormatException | NullPointerException e) { + throw new IllegalArgumentException(errorMessage, e); + } + } + + private static float parseFloat(String text, String errorMessage) { + try { + return Float.parseFloat(text); + } catch (NumberFormatException | NullPointerException e) { + throw new IllegalArgumentException(errorMessage, e); + } + } + @Override public void onHelp() { try (PrintWriter pw = getOutPrintWriter();) { pw.println("Vibrator Manager commands:"); pw.println(" help"); pw.println(" Prints this help text."); - pw.println(""); pw.println(" list"); - pw.println(" Prints the id of device vibrators. This does not include any "); - pw.println(" connected input device."); + pw.println(" Prints device vibrator ids; does not include input devices."); pw.println(" synced [options] <effect>..."); pw.println(" Vibrates effect on all vibrators in sync."); pw.println(" combined [options] (-v <vibrator-id> <effect>...)..."); @@ -2941,58 +3104,63 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { pw.println(" xml [options] <xml>"); pw.println(" Vibrates using combined vibration described in given XML string"); pw.println(" on all vibrators in sync. The XML could be:"); - pw.println(" XML containing a single effect, or"); - pw.println(" A vibration select XML containing multiple effects."); - pw.println(" Vibrates using combined vibration described in given XML string."); - pw.println(" XML containing a single effect it runs on all vibrators in sync."); + pw.println(" A single <vibration-effect>, or"); + pw.println(" A <vibration-select> containing multiple effects."); + pw.println(" feedback [options] <constant>"); + pw.println(" Performs a haptic feedback with the given constant."); pw.println(" cancel"); pw.println(" Cancels any active vibration"); - pw.println(" feedback [-f] [-d <description>] <constant>"); - pw.println(" Performs a haptic feedback with the given constant."); - pw.println(" The force (-f) option enables the `always` configuration, which"); - pw.println(" plays the haptic irrespective of the vibration intensity settings"); pw.println(""); pw.println("Effect commands:"); pw.println(" oneshot [-w delay] [-a] <duration> [<amplitude>]"); - pw.println(" Vibrates for duration milliseconds; ignored when device is on "); - pw.println(" DND (Do Not Disturb) mode; touch feedback strength user setting "); - pw.println(" will be used to scale amplitude."); + pw.println(" Vibrates for duration milliseconds."); pw.println(" If -w is provided, the effect will be played after the specified"); pw.println(" wait time in milliseconds."); pw.println(" If -a is provided, the command accepts a second argument for "); pw.println(" amplitude, in a scale of 1-255."); pw.print(" waveform [-w delay] [-r index] [-a] [-f] [-c] "); pw.println("(<duration> [<amplitude>] [<frequency>])..."); - pw.println(" Vibrates for durations and amplitudes in list; ignored when "); - pw.println(" device is on DND (Do Not Disturb) mode; touch feedback strength "); - pw.println(" user setting will be used to scale amplitude."); + pw.println(" Vibrates for durations and amplitudes in list."); pw.println(" If -w is provided, the effect will be played after the specified"); pw.println(" wait time in milliseconds."); pw.println(" If -r is provided, the waveform loops back to the specified"); - pw.println(" index (e.g. 0 loops from the beginning)"); + pw.println(" index (e.g. 0 loops from the beginning)."); pw.println(" If -a is provided, the command expects amplitude to follow each"); pw.println(" duration; otherwise, it accepts durations only and alternates"); - pw.println(" off/on"); + pw.println(" off/on."); pw.println(" If -f is provided, the command expects frequency to follow each"); - pw.println(" amplitude or duration; otherwise, it uses resonant frequency"); + pw.println(" amplitude or duration; otherwise, it uses resonant frequency."); pw.println(" If -c is provided, the waveform is continuous and will ramp"); pw.println(" between values; otherwise each entry is a fixed step."); pw.println(" Duration is in milliseconds; amplitude is a scale of 1-255;"); - pw.println(" frequency is an absolute value in hertz;"); + pw.println(" frequency is an absolute value in hertz."); + pw.print(" envelope [-a] [-i initial sharpness] [-r index] "); + pw.println("[<duration1> <intensity1> <sharpness1>]..."); + pw.println(" Generates a vibration pattern based on a series of duration, "); + pw.println(" intensity, and sharpness values. The total vibration time is "); + pw.println(" the sum of all durations."); + pw.println(" If -a is provided, the waveform will use the advanced APIs to "); + pw.println(" generate the vibration pattern and the input parameters "); + pw.println(" become [<duration1> <amplitude1> <frequency1>]."); + pw.println(" If -i is provided, the waveform will have an initial sharpness "); + pw.println(" it will start from."); + pw.println(" If -r is provided, the waveform loops back to the specified index"); + pw.println(" (e.g. 0 loops from the beginning)."); pw.println(" prebaked [-w delay] [-b] <effect-id>"); - pw.println(" Vibrates with prebaked effect; ignored when device is on DND "); - pw.println(" (Do Not Disturb) mode; touch feedback strength user setting "); - pw.println(" will be used to scale amplitude."); + pw.println(" Vibrates with prebaked effect."); pw.println(" If -w is provided, the effect will be played after the specified"); pw.println(" wait time in milliseconds."); pw.println(" If -b is provided, the prebaked fallback effect will be played if"); pw.println(" the device doesn't support the given effect-id."); - pw.println(" primitives ([-w delay] <primitive-id>)..."); - pw.println(" Vibrates with a composed effect; ignored when device is on DND "); - pw.println(" (Do Not Disturb) mode; touch feedback strength user setting "); - pw.println(" will be used to scale primitive intensities."); + pw.print(" primitives ([-w delay] [-o time] [-s scale]"); + pw.println("<primitive-id> [<scale>])..."); + pw.println(" Vibrates with a composed effect."); pw.println(" If -w is provided, the next primitive will be played after the "); pw.println(" specified wait time in milliseconds."); + pw.println(" If -o is provided, the next primitive will be played at the "); + pw.println(" specified start offset time in milliseconds."); + pw.println(" If -s is provided, the next primitive will be played with the"); + pw.println(" specified amplitude scale, in a scale of [0,1]."); pw.println(""); pw.println("Common Options:"); pw.println(" -f"); @@ -3003,6 +3171,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { pw.println(" -d <description>"); pw.println(" Add description to the vibration."); pw.println(""); + pw.println("Notes"); + pw.println(" Vibrations triggered by these commands will be ignored when"); + pw.println(" device is on DND (Do Not Disturb) mode; notification strength"); + pw.println(" user settings will be applied for scale."); + pw.println(""); } } } diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 6cc17d4fa009..a94183849bc5 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -857,8 +857,7 @@ class ActivityMetricsLogger { info.mWindowsDrawnDelayMs = info.calculateDelay(timestampNs); info.mIsDrawn = true; final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info); - if (info.mLoggedTransitionStarting || (!r.mDisplayContent.mOpeningApps.contains(r) - && !r.mTransitionController.isCollecting(r))) { + if (info.mLoggedTransitionStarting || !r.mTransitionController.isCollecting(r)) { done(false /* abort */, info, "notifyWindowsDrawn", timestampNs); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 03fe7775edb0..aa3b500c05f6 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -159,7 +159,6 @@ import static com.android.server.wm.ActivityRecordProto.FRONT_OF_TASK; import static com.android.server.wm.ActivityRecordProto.IN_SIZE_COMPAT_MODE; import static com.android.server.wm.ActivityRecordProto.IS_ANIMATING; import static com.android.server.wm.ActivityRecordProto.IS_USER_FULLSCREEN_OVERRIDE_ENABLED; -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_DROP_INPUT_MODE; import static com.android.server.wm.ActivityRecordProto.LAST_SURFACE_SHOWING; @@ -290,6 +289,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.UserProperties; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.PixelFormat; @@ -330,7 +330,6 @@ import android.view.RemoteAnimationDefinition; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets; -import android.view.WindowInsets.Type; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.WindowManager.TransitionOldType; @@ -354,7 +353,6 @@ import com.android.internal.app.ResolverActivity; import com.android.internal.content.ReferrerIntent; import com.android.internal.os.TimeoutRecord; import com.android.internal.os.TransferPipe; -import com.android.internal.policy.AttributeCache; import com.android.internal.policy.PhoneWindow; import com.android.internal.protolog.ProtoLog; import com.android.internal.util.XmlUtils; @@ -481,6 +479,8 @@ final class ActivityRecord extends WindowToken { final String processName; // process where this component wants to run final String taskAffinity; // as per ActivityInfo.taskAffinity final boolean stateNotNeeded; // As per ActivityInfo.flags + @Nullable + final WindowStyle mWindowStyle; @VisibleForTesting int mHandoverLaunchDisplayId = INVALID_DISPLAY; // Handover launch display id to next activity. @VisibleForTesting @@ -493,6 +493,7 @@ final class ActivityRecord extends WindowToken { private long createTime = System.currentTimeMillis(); long lastVisibleTime; // last time this activity became visible long pauseTime; // last time we started pausing the activity + long mStoppedTime; // last time we completely stopped the activity long launchTickTime; // base time for launch tick messages long topResumedStateLossTime; // last time we reported top resumed state loss to an activity // Last configuration reported to the activity in the client process. @@ -1519,17 +1520,7 @@ final class ActivityRecord extends WindowToken { this.task = newTask; if (shouldStartChangeTransition(newParent, oldParent)) { - if (mTransitionController.isShellTransitionsEnabled()) { - // For Shell transition, call #initializeChangeTransition directly to take the - // screenshot at the Activity level. And Shell will be in charge of handling the - // surface reparent and crop. - initializeChangeTransition(getBounds()); - } else { - // For legacy app transition, we want to take a screenshot of the Activity surface, - // but animate the change transition on TaskFragment level to get the correct window - // crop. - newParent.initializeChangeTransition(getBounds(), getSurfaceControl()); - } + mTransitionController.collectVisibleChange(this); } super.onParentChanged(newParent, oldParent); @@ -1557,16 +1548,6 @@ final class ActivityRecord extends WindowToken { mLastReportedPictureInPictureMode = inPinnedWindowingMode(); } - // When the associated task is {@code null}, the {@link ActivityRecord} can no longer - // access visual elements like the {@link DisplayContent}. We must remove any associations - // such as animations. - if (task == null) { - // It is possible we have been marked as a closing app earlier. We must remove ourselves - // from this list so we do not participate in any future animations. - if (getDisplayContent() != null) { - getDisplayContent().mClosingApps.remove(this); - } - } final Task rootTask = getRootTask(); if (task == mLastParentBeforePip && task != null) { // Notify the TaskFragmentOrganizer that the activity is reparented back from pip. @@ -1749,14 +1730,6 @@ final class ActivityRecord extends WindowToken { return; } prevDc.onRunningActivityChanged(); - - if (prevDc.mOpeningApps.remove(this)) { - // Transfer opening transition to new display. - mDisplayContent.mOpeningApps.add(this); - mDisplayContent.executeAppTransition(); - } - - prevDc.mClosingApps.remove(this); prevDc.getDisplayPolicy().removeRelaunchingApp(this); if (prevDc.mFocusedApp == this) { @@ -1956,22 +1929,17 @@ final class ActivityRecord extends WindowToken { ? android.R.style.Theme : android.R.style.Theme_Holo; } - final AttributeCache.Entry ent = AttributeCache.instance().get(packageName, - realTheme, com.android.internal.R.styleable.Window, mUserId); - - if (ent != null) { - final boolean styleTranslucent = ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowIsTranslucent, false); - final boolean styleFloating = ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowIsFloating, false); - mOccludesParent = !(styleTranslucent || styleFloating) + final WindowStyle style = mAtmService.getWindowStyle(packageName, realTheme, mUserId); + mWindowStyle = style; + if (style != null) { + mOccludesParent = !(style.isTranslucent() || style.isFloating()) // This style is propagated to the main window attributes with // FLAG_SHOW_WALLPAPER from PhoneWindow#generateLayout. - || ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false); + || style.showWallpaper(); mStyleFillsParent = mOccludesParent; - mNoDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false); - mOptOutEdgeToEdge = PhoneWindow.isOptingOutEdgeToEdgeEnforcement( - aInfo.applicationInfo, false /* local */, ent.array); + mNoDisplay = style.noDisplay(); + mOptOutEdgeToEdge = style.optOutEdgeToEdge() && PhoneWindow.isOptOutEdgeToEdgeEnabled( + aInfo.applicationInfo, false /* local */); } else { mStyleFillsParent = mOccludesParent = true; mNoDisplay = false; @@ -2302,21 +2270,17 @@ final class ActivityRecord extends WindowToken { return false; } - final AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme, - com.android.internal.R.styleable.Window, mWmService.mCurrentUserId); - if (ent == null) { + final WindowStyle style = theme == this.theme + ? mWindowStyle : mAtmService.getWindowStyle(pkg, theme, mUserId); + if (style == null) { // Whoops! App doesn't exist. Um. Okay. We'll just pretend like we didn't // see that. return false; } - final boolean windowIsTranslucent = ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowIsTranslucent, false); - final boolean windowIsFloating = ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowIsFloating, false); - final boolean windowShowWallpaper = ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowShowWallpaper, false); - final boolean windowDisableStarting = ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowDisablePreview, false); + final boolean windowIsTranslucent = style.isTranslucent(); + final boolean windowIsFloating = style.isFloating(); + final boolean windowShowWallpaper = style.showWallpaper(); + final boolean windowDisableStarting = style.disablePreview(); ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s", windowIsTranslucent, windowIsFloating, windowShowWallpaper, @@ -4392,7 +4356,6 @@ final class ActivityRecord extends WindowToken { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Removing app token: %s", this); - getDisplayContent().mOpeningApps.remove(this); getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this); mWmService.mSnapshotController.onAppRemoved(this); mAtmService.mStartingProcessActivities.remove(this); @@ -4404,20 +4367,9 @@ final class ActivityRecord extends WindowToken { mAppCompatController.getTransparentPolicy().stop(); // 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 - // activity while a parent is animating. - boolean delayed = isAnimating(TRANSITION | PARENTS | CHILDREN, - ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION); - if (getDisplayContent().mClosingApps.contains(this)) { - delayed = true; - } else if (getDisplayContent().mAppTransition.isTransitionSet()) { - getDisplayContent().mClosingApps.add(this); - delayed = true; - } else if (mTransitionController.inTransition()) { - delayed = true; - } - + // going. The handleCompleteDeferredRemoval will continue the removal. + final boolean delayed = isAnimating(CHILDREN, ANIMATION_TYPE_WINDOW_ANIMATION) + || mTransitionController.inTransition(); // Don't commit visibility if it is waiting to animate. It will be set post animation. if (!delayed) { commitVisibility(false /* visible */, true /* performLayout */); @@ -5552,9 +5504,6 @@ final class ActivityRecord extends WindowToken { mAtmService.mBackNavigationController.onAppVisibilityChanged(this, visible); - final DisplayContent displayContent = getDisplayContent(); - displayContent.mOpeningApps.remove(this); - displayContent.mClosingApps.remove(this); setVisibleRequested(visible); mLastDeferHidingClient = deferHidingClient; @@ -5567,13 +5516,6 @@ final class ActivityRecord extends WindowToken { setClientVisible(false); } } else { - if (!appTransition.isTransitionSet() - && appTransition.isReady()) { - // Add the app mOpeningApps if transition is unset but ready. This means - // we're doing a screen freeze, and the unfreeze will wait for all opening - // apps to be ready. - displayContent.mOpeningApps.add(this); - } startingMoved = false; // If the token is currently hidden (should be the common case), or has been // stopped, then we need to set up to wait for its windows to be ready. @@ -6506,6 +6448,7 @@ final class ActivityRecord extends WindowToken { Slog.w(TAG, "Exception thrown during pause", e); // Just in case, assume it to be stopped. mAppStopped = true; + mStoppedTime = SystemClock.uptimeMillis(); ProtoLog.v(WM_DEBUG_STATES, "Stop failed; moving to STOPPED: %s", this); setState(STOPPED, "stopIfPossible"); } @@ -6539,6 +6482,7 @@ final class ActivityRecord extends WindowToken { if (isStopping) { ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPED: %s (stop complete)", this); + mStoppedTime = SystemClock.uptimeMillis(); setState(STOPPED, "activityStopped"); } @@ -6775,7 +6719,7 @@ final class ActivityRecord extends WindowToken { setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow"); // We can now show all of the drawn windows! - if (!getDisplayContent().mOpeningApps.contains(this) && canShowWindows()) { + if (canShowWindows()) { showAllWindowsLocked(); } } @@ -7187,14 +7131,10 @@ final class ActivityRecord extends WindowToken { if (theme == 0) { return false; } - final AttributeCache.Entry ent = AttributeCache.instance().get(packageName, theme, - R.styleable.Window, mWmService.mCurrentUserId); - if (ent != null) { - if (ent.array.hasValue(R.styleable.Window_windowSplashScreenBehavior)) { - return ent.array.getInt(R.styleable.Window_windowSplashScreenBehavior, - SPLASH_SCREEN_BEHAVIOR_DEFAULT) - == SPLASH_SCREEN_BEHAVIOR_ICON_PREFERRED; - } + final WindowStyle style = theme == this.theme + ? mWindowStyle : mAtmService.getWindowStyle(packageName, theme, mUserId); + if (style != null) { + return style.mSplashScreenBehavior == SPLASH_SCREEN_BEHAVIOR_ICON_PREFERRED; } return false; } @@ -7449,15 +7389,6 @@ final class ActivityRecord extends WindowToken { return boundsLayer; } - @Override - boolean isWaitingForTransitionStart() { - final DisplayContent dc = getDisplayContent(); - return dc != null && dc.mAppTransition.isTransitionSet() - && (dc.mOpeningApps.contains(this) - || dc.mClosingApps.contains(this) - || dc.mChangingContainers.contains(this)); - } - boolean isTransitionForward() { return (mStartingData != null && mStartingData.mIsTransitionForward) || mDisplayContent.isNextTransitionForward(); @@ -8051,6 +7982,7 @@ final class ActivityRecord extends WindowToken { mConfigurationSeq = Math.max(++mConfigurationSeq, 1); getResolvedOverrideConfiguration().seq = mConfigurationSeq; + // TODO(b/392069771): Move to AppCompatSandboxingPolicy. // Sandbox max bounds by setting it to the activity bounds, if activity is letterboxed, or // has or will have mAppCompatDisplayInsets for size compat. Also forces an activity to be // sandboxed or not depending upon the configuration settings. @@ -8079,6 +8011,9 @@ final class ActivityRecord extends WindowToken { resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); } + mAppCompatController.getSandboxingPolicy().sandboxBoundsIfNeeded(resolvedConfig, + parentWindowingMode); + applySizeOverrideIfNeeded( mDisplayContent, info.applicationInfo, @@ -9557,7 +9492,6 @@ final class ActivityRecord extends WindowToken { writeNameToProto(proto, NAME); super.dumpDebug(proto, WINDOW_TOKEN, logLevel); proto.write(LAST_SURFACE_SHOWING, mLastSurfaceShowing); - proto.write(IS_WAITING_FOR_TRANSITION_START, isWaitingForTransitionStart()); proto.write(IS_ANIMATING, isAnimating(TRANSITION | PARENTS | CHILDREN, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION)); proto.write(FILLS_PARENT, fillsParent()); @@ -9713,7 +9647,18 @@ final class ActivityRecord extends WindowToken { && !mDisplayContent.isSleeping()) { // Visibility of starting activities isn't calculated until pause-complete, so if // this is not paused yet, don't consider it ready. - return false; + // However, due to pip1 having an intermediate state, add a special exception here + // that skips waiting if the next activity is already visible. + final ActivityRecord toResume = isPip2ExperimentEnabled() ? null + : mDisplayContent.getActivity((r) -> !r.finishing + && r.isVisibleRequested() + && !r.isTaskOverlay() + && !r.isAlwaysOnTop()); + if (toResume == null || !toResume.isVisible()) { + return false; + } else { + Slog.i(TAG, "Assuming sync-finish while pausing due to visible target"); + } } return true; } @@ -9872,6 +9817,68 @@ final class ActivityRecord extends WindowToken { int mBackgroundColor; } + static class WindowStyle { + private static final int FLAG_IS_TRANSLUCENT = 1; + private static final int FLAG_IS_FLOATING = 1 << 1; + private static final int FLAG_SHOW_WALLPAPER = 1 << 2; + private static final int FLAG_NO_DISPLAY = 1 << 3; + private static final int FLAG_DISABLE_PREVIEW = 1 << 4; + private static final int FLAG_OPT_OUT_EDGE_TO_EDGE = 1 << 5; + + final int mFlags; + + @SplashScreenBehavior + final int mSplashScreenBehavior; + + WindowStyle(TypedArray array) { + int flags = 0; + if (array.getBoolean(R.styleable.Window_windowIsTranslucent, false)) { + flags |= FLAG_IS_TRANSLUCENT; + } + if (array.getBoolean(R.styleable.Window_windowIsFloating, false)) { + flags |= FLAG_IS_FLOATING; + } + if (array.getBoolean(R.styleable.Window_windowShowWallpaper, false)) { + flags |= FLAG_SHOW_WALLPAPER; + } + if (array.getBoolean(R.styleable.Window_windowNoDisplay, false)) { + flags |= FLAG_NO_DISPLAY; + } + if (array.getBoolean(R.styleable.Window_windowDisablePreview, false)) { + flags |= FLAG_DISABLE_PREVIEW; + } + if (array.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false)) { + flags |= FLAG_OPT_OUT_EDGE_TO_EDGE; + } + mFlags = flags; + mSplashScreenBehavior = array.getInt(R.styleable.Window_windowSplashScreenBehavior, 0); + } + + boolean isTranslucent() { + return (mFlags & FLAG_IS_TRANSLUCENT) != 0; + } + + boolean isFloating() { + return (mFlags & FLAG_IS_FLOATING) != 0; + } + + boolean showWallpaper() { + return (mFlags & FLAG_SHOW_WALLPAPER) != 0; + } + + boolean noDisplay() { + return (mFlags & FLAG_NO_DISPLAY) != 0; + } + + boolean disablePreview() { + return (mFlags & FLAG_DISABLE_PREVIEW) != 0; + } + + boolean optOutEdgeToEdge() { + return (mFlags & FLAG_OPT_OUT_EDGE_TO_EDGE) != 0; + } + } + static class Builder { private final ActivityTaskManagerService mAtmService; private WindowProcessController mCallerApp; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 0a57cb50d681..c243cdc23137 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -617,6 +617,9 @@ public abstract class ActivityTaskManagerInternal { */ public abstract boolean isBaseOfLockedTask(String packageName); + /** Returns the value of {@link android.R.attr#windowNoDisplay} from the given theme. */ + public abstract boolean isNoDisplay(String packageName, int theme, int userId); + /** * Creates an interface to update configuration for the calling application. */ diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 819e117e6d05..6f83822ee97a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -286,6 +286,7 @@ import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.NeededUriGrants; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.wallpaper.WallpaperManagerInternal; +import com.android.server.wm.utils.WindowStyleCache; import com.android.wm.shell.Flags; import java.io.BufferedReader; @@ -500,6 +501,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { boolean mSuppressResizeConfigChanges; + private final WindowStyleCache<ActivityRecord.WindowStyle> mWindowStyleCache = + new WindowStyleCache<>(ActivityRecord.WindowStyle::new); final UpdateConfigurationResult mTmpUpdateConfigurationResult = new UpdateConfigurationResult(); @@ -2127,6 +2130,26 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public boolean setTaskIsPerceptible(int taskId, boolean isPerceptible) { + enforceTaskPermission("setTaskIsPerceptible()"); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + final Task task = mRootWindowContainer.anyTaskForId(taskId, + MATCH_ATTACHED_TASK_ONLY); + if (task == null) { + Slog.w(TAG, "setTaskIsPerceptible: No task to set with id=" + taskId); + return false; + } + task.mIsPerceptible = isPerceptible; + } + return true; + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public boolean removeTask(int taskId) { mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeTask()"); synchronized (mGlobalLock) { @@ -5570,6 +5593,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mUserManagerInternal; } + @Nullable + ActivityRecord.WindowStyle getWindowStyle(String packageName, int theme, int userId) { + if (!com.android.window.flags.Flags.cacheWindowStyle()) { + final AttributeCache.Entry ent = AttributeCache.instance().get(packageName, + theme, com.android.internal.R.styleable.Window, userId); + return ent != null ? new ActivityRecord.WindowStyle(ent.array) : null; + } + return mWindowStyleCache.get(packageName, theme, userId); + } + AppWarnings getAppWarningsLocked() { return mAppWarnings; } @@ -6518,6 +6551,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mCompatModePackages.handlePackageUninstalledLocked(name); mPackageConfigPersister.onPackageUninstall(name, userId); } + mWindowStyleCache.invalidatePackage(name); } @Override @@ -6534,6 +6568,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (mRootWindowContainer == null) return; mRootWindowContainer.updateActivityApplicationInfo(aInfo); } + mWindowStyleCache.invalidatePackage(aInfo.packageName); } @Override @@ -7433,6 +7468,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public boolean isNoDisplay(String packageName, int theme, int userId) { + if (!com.android.window.flags.Flags.cacheWindowStyle()) { + final AttributeCache.Entry ent = AttributeCache.instance() + .get(packageName, theme, R.styleable.Window, userId); + return ent != null + && ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false); + } + final ActivityRecord.WindowStyle style = getWindowStyle(packageName, theme, userId); + return style != null && style.noDisplay(); + } + + @Override public PackageConfigurationUpdater createPackageConfigurationUpdater() { return new PackageConfigurationUpdaterImpl(Binder.getCallingPid(), ActivityTaskManagerService.this); diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 6a5adca91e39..463a92fb55bb 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -145,6 +145,7 @@ import android.util.SparseIntArray; import android.view.Display; import android.webkit.URLUtil; import android.window.ActivityWindowInfo; +import android.window.DesktopExperienceFlags; import android.window.DesktopModeFlags; import com.android.internal.R; @@ -899,108 +900,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { lockTaskController.startLockTaskMode(task, false, 0 /* blank UID */); } - try { - if (!proc.hasThread()) { - throw new RemoteException(); - } - List<ResultInfo> results = null; - List<ReferrerIntent> newIntents = null; - if (andResume) { - // We don't need to deliver new intents and/or set results if activity is going - // to pause immediately after launch. - results = r.results; - newIntents = r.newIntents; - } - if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, - "Launching: " + r + " savedState=" + r.getSavedState() - + " with results=" + results + " newIntents=" + newIntents - + " andResume=" + andResume); - EventLogTags.writeWmRestartActivity(r.mUserId, System.identityHashCode(r), - task.mTaskId, r.shortComponentName); - updateHomeProcessIfNeeded(r); - mService.getPackageManagerInternalLocked().notifyPackageUse( - r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY); - mService.getAppWarningsLocked().onStartActivity(r); - - final Configuration procConfig = proc.prepareConfigurationForLaunchingActivity(); - final Configuration overrideConfig = r.getMergedOverrideConfiguration(); - r.setLastReportedConfiguration(procConfig, overrideConfig); - - final ActivityWindowInfo activityWindowInfo = r.getActivityWindowInfo(); - r.setLastReportedActivityWindowInfo(activityWindowInfo); - - logIfTransactionTooLarge(r.intent, r.getSavedState()); - - final TaskFragment organizedTaskFragment = r.getOrganizedTaskFragment(); - if (organizedTaskFragment != null) { - // Sending TaskFragmentInfo to client to ensure the info is updated before - // the activity creation. - mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent( - organizedTaskFragment); - } - - // Create activity launch transaction. - final boolean isTransitionForward = r.isTransitionForward(); - final IBinder fragmentToken = r.getTaskFragment().getFragmentToken(); - final int deviceId = getDeviceIdForDisplayId(r.getDisplayId()); - final LaunchActivityItem launchActivityItem = new LaunchActivityItem(r.token, - r.intent, System.identityHashCode(r), r.info, - procConfig, overrideConfig, deviceId, - r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor, - proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(), - results, newIntents, r.takeSceneTransitionInfo(), isTransitionForward, - proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController, - r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken, - r.initialCallerInfoAccessToken, activityWindowInfo); - - // Set desired final state. - final ActivityLifecycleItem lifecycleItem; - if (andResume) { - lifecycleItem = new ResumeActivityItem(r.token, isTransitionForward, - r.shouldSendCompatFakeFocus()); - } else if (r.isVisibleRequested()) { - lifecycleItem = new PauseActivityItem(r.token); - } else { - lifecycleItem = new StopActivityItem(r.token); - } - - // Schedule transaction. - if (shouldDispatchLaunchActivityItemIndependently(r.info.packageName, r.getUid())) { - // LaunchActivityItem has @UnsupportedAppUsage usages. - // Guard with targetSDK on Android 15+. - // To not bundle the transaction, dispatch the pending before schedule new - // transaction. - mService.getLifecycleManager().dispatchPendingTransaction(proc.getThread()); - } - mService.getLifecycleManager().scheduleTransactionItems( - proc.getThread(), - // Immediately dispatch the transaction, so that if it fails, the server can - // restart the process and retry now. - true /* shouldDispatchImmediately */, - launchActivityItem, lifecycleItem); - - if (procConfig.seq > mRootWindowContainer.getConfiguration().seq) { - // If the seq is increased, there should be something changed (e.g. registered - // activity configuration). - proc.setLastReportedConfiguration(procConfig); - } - if ((proc.mInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0 - && mService.mHasHeavyWeightFeature) { - // This may be a heavy-weight process! Note that the package manager will ensure - // that only activity can run in the main process of the .apk, which is the only - // thing that will be considered heavy-weight. - if (proc.mName.equals(proc.mInfo.packageName)) { - if (mService.mHeavyWeightProcess != null - && mService.mHeavyWeightProcess != proc) { - Slog.w(TAG, "Starting new heavy weight process " + proc - + " when already running " - + mService.mHeavyWeightProcess); - } - mService.setHeavyWeightProcess(r); - } - } - - } catch (RemoteException e) { + final RemoteException e = tryRealStartActivityInner( + task, r, proc, activityClientController, andResume); + if (e != null) { if (r.launchFailed) { // This is the second time we failed -- finish activity and give up. Slog.e(TAG, "Second failure launching " @@ -1023,7 +925,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { r.launchFailed = false; - // TODO(lifecycler): Resume or pause requests are done as part of launch transaction, + // Resume or pause requests are done as part of launch transaction, // so updating the state should be done accordingly. if (andResume && readyToResume()) { // As part of the process of launching, ActivityThread also performs @@ -1063,6 +965,122 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { return true; } + /** @return {@link RemoteException} if the app process failed to handle the activity start. */ + @Nullable + private RemoteException tryRealStartActivityInner( + @NonNull Task task, + @NonNull ActivityRecord r, + @NonNull WindowProcessController proc, + @Nullable IActivityClientController activityClientController, + boolean andResume) { + if (!proc.hasThread()) { + return new RemoteException(); + } + List<ResultInfo> results = null; + List<ReferrerIntent> newIntents = null; + if (andResume) { + // We don't need to deliver new intents and/or set results if activity is going + // to pause immediately after launch. + results = r.results; + newIntents = r.newIntents; + } + if (DEBUG_SWITCH) { + Slog.v(TAG_SWITCH, + "Launching: " + r + " savedState=" + r.getSavedState() + + " with results=" + results + " newIntents=" + newIntents + + " andResume=" + andResume); + } + EventLogTags.writeWmRestartActivity(r.mUserId, System.identityHashCode(r), + task.mTaskId, r.shortComponentName); + updateHomeProcessIfNeeded(r); + mService.getPackageManagerInternalLocked().notifyPackageUse( + r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY); + mService.getAppWarningsLocked().onStartActivity(r); + + final Configuration procConfig = proc.prepareConfigurationForLaunchingActivity(); + final Configuration overrideConfig = r.getMergedOverrideConfiguration(); + r.setLastReportedConfiguration(procConfig, overrideConfig); + + final ActivityWindowInfo activityWindowInfo = r.getActivityWindowInfo(); + r.setLastReportedActivityWindowInfo(activityWindowInfo); + + logIfTransactionTooLarge(r.intent, r.getSavedState()); + + final TaskFragment organizedTaskFragment = r.getOrganizedTaskFragment(); + if (organizedTaskFragment != null) { + // Sending TaskFragmentInfo to client to ensure the info is updated before + // the activity creation. + mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent( + organizedTaskFragment); + } + + // Create activity launch transaction. + final boolean isTransitionForward = r.isTransitionForward(); + final IBinder fragmentToken = r.getTaskFragment().getFragmentToken(); + final int deviceId = getDeviceIdForDisplayId(r.getDisplayId()); + final LaunchActivityItem launchActivityItem = new LaunchActivityItem(r.token, + r.intent, System.identityHashCode(r), r.info, + procConfig, overrideConfig, deviceId, + r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor, + proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(), + results, newIntents, r.takeSceneTransitionInfo(), isTransitionForward, + proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController, + r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken, + r.initialCallerInfoAccessToken, activityWindowInfo); + + // Set desired final state. + final ActivityLifecycleItem lifecycleItem; + if (andResume) { + lifecycleItem = new ResumeActivityItem(r.token, isTransitionForward, + r.shouldSendCompatFakeFocus()); + } else if (r.isVisibleRequested()) { + lifecycleItem = new PauseActivityItem(r.token); + } else { + lifecycleItem = new StopActivityItem(r.token); + } + + // Schedule transaction. + if (shouldDispatchLaunchActivityItemIndependently(r.info.packageName, r.getUid())) { + // LaunchActivityItem has @UnsupportedAppUsage usages. + // Guard with targetSDK on Android 15+. + // To not bundle the transaction, dispatch the pending before schedule new + // transaction. + mService.getLifecycleManager().dispatchPendingTransaction(proc.getThread()); + } + try { + mService.getLifecycleManager().scheduleTransactionItems( + proc.getThread(), + // Immediately dispatch the transaction, so that if it fails, the server can + // restart the process and retry now. + true /* shouldDispatchImmediately */, + launchActivityItem, lifecycleItem); + } catch (RemoteException e) { + return e; + } + + if (procConfig.seq > mRootWindowContainer.getConfiguration().seq) { + // If the seq is increased, there should be something changed (e.g. registered + // activity configuration). + proc.setLastReportedConfiguration(procConfig); + } + if ((proc.mInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0 + && mService.mHasHeavyWeightFeature) { + // This may be a heavy-weight process! Note that the package manager will ensure + // that only activity can run in the main process of the .apk, which is the only + // thing that will be considered heavy-weight. + if (proc.mName.equals(proc.mInfo.packageName)) { + if (mService.mHeavyWeightProcess != null + && mService.mHeavyWeightProcess != proc) { + Slog.w(TAG, "Starting new heavy weight process " + proc + + " when already running " + + mService.mHeavyWeightProcess); + } + mService.setHeavyWeightProcess(r); + } + } + return null; + } + void updateHomeProcessIfNeeded(@NonNull ActivityRecord r) { if (!r.isActivityTypeHome()) return; // Make sure that we use the bottom most activity from the same package, because the home @@ -2916,6 +2934,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { /** The helper to calculate whether a container is opaque. */ static class OpaqueContainerHelper implements Predicate<ActivityRecord> { + private final boolean mEnableMultipleDesktopsBackend = + DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue(); private ActivityRecord mStarting; private boolean mIgnoringInvisibleActivity; private boolean mIgnoringKeyguard; @@ -2938,7 +2958,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { mIgnoringKeyguard = ignoringKeyguard; final boolean isOpaque; - if (!Flags.enableMultipleDesktopsBackend()) { + if (!mEnableMultipleDesktopsBackend) { isOpaque = container.getActivity(this, true /* traverseTopToBottom */, null /* boundary */) != null; } else { @@ -2949,13 +2969,16 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } private boolean isOpaqueInner(@NonNull WindowContainer<?> container) { - // If it's a leaf task fragment, then opacity is calculated based on its activities. - if (container.asTaskFragment() != null - && ((TaskFragment) container).isLeafTaskFragment()) { + final boolean isActivity = container.asActivityRecord() != null; + final boolean isLeafTaskFragment = container.asTaskFragment() != null + && ((TaskFragment) container).isLeafTaskFragment(); + if (isActivity || isLeafTaskFragment) { + // When it is an activity or leaf task fragment, then opacity is calculated based + // on itself or its activities. return container.getActivity(this, true /* traverseTopToBottom */, null /* boundary */) != null; } - // When not a leaf, it's considered opaque if any of its opaque children fill this + // Otherwise, it's considered opaque if any of its opaque children fill this // container, unless the children are adjacent fragments, in which case as long as they // are all opaque then |container| is also considered opaque, even if the adjacent // task fragment aren't filling. diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index bed95face1c9..fc504796b0ac 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -44,6 +44,8 @@ class AppCompatController { private final AppCompatLetterboxPolicy mLetterboxPolicy; @NonNull private final AppCompatSizeCompatModePolicy mSizeCompatModePolicy; + @NonNull + private final AppCompatSandboxingPolicy mSandboxingPolicy; AppCompatController(@NonNull WindowManagerService wmService, @NonNull ActivityRecord activityRecord) { @@ -66,6 +68,7 @@ class AppCompatController { mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration); mSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(activityRecord, mAppCompatOverrides); + mSandboxingPolicy = new AppCompatSandboxingPolicy(activityRecord); } @NonNull @@ -143,6 +146,11 @@ class AppCompatController { return mSizeCompatModePolicy; } + @NonNull + AppCompatSandboxingPolicy getSandboxingPolicy() { + return mSandboxingPolicy; + } + void dump(@NonNull PrintWriter pw, @NonNull String prefix) { getTransparentPolicy().dump(pw, prefix); getLetterboxPolicy().dump(pw, prefix); diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java index dff072e2dcf8..57811e24351f 100644 --- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java @@ -16,12 +16,14 @@ 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.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.window.DesktopModeFlags.EXCLUDE_CAPTION_FROM_APP_BOUNDS; import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; import static com.android.server.wm.AppCompatConfiguration.letterboxBackgroundTypeToString; @@ -48,6 +50,8 @@ import java.io.PrintWriter; */ class AppCompatLetterboxPolicy { + private static final int DIFF_TOLERANCE_PX = 1; + @NonNull private final ActivityRecord mActivityRecord; @NonNull @@ -56,6 +60,9 @@ class AppCompatLetterboxPolicy { private final AppCompatRoundedCorners mAppCompatRoundedCorners; @NonNull private final AppCompatConfiguration mAppCompatConfiguration; + // Convenience temporary object to save allocation when calculating Rect. + @NonNull + private final Rect mTmpRect = new Rect(); private boolean mLastShouldShowLetterboxUi; @@ -71,7 +78,7 @@ class AppCompatLetterboxPolicy { : new LegacyLetterboxPolicyState(); // TODO (b/358334569) Improve cutout logic dependency on app compat. mAppCompatRoundedCorners = new AppCompatRoundedCorners(mActivityRecord, - this::isLetterboxedNotForDisplayCutout); + this::ieEligibleForRoundedCorners); mAppCompatConfiguration = appCompatConfiguration; } @@ -84,7 +91,7 @@ class AppCompatLetterboxPolicy { mLetterboxPolicyState.stop(); } - /** @return {@value true} if the letterbox policy is running and the activity letterboxed. */ + /** @return {@code true} if the letterbox policy is running and the activity letterboxed. */ boolean isRunning() { return mLetterboxPolicyState.isRunning(); } @@ -130,7 +137,7 @@ class AppCompatLetterboxPolicy { * <li>The activity is in fullscreen. * <li>The activity is portrait-only. * <li>The activity doesn't have a starting window (education should only be displayed - * once the starting window is removed in {@link #removeStartingWindow}). + * once the starting window is removed in {@link ActivityRecord#removeStartingWindow}). * </ul> */ boolean isEligibleForLetterboxEducation() { @@ -294,16 +301,40 @@ class AppCompatLetterboxPolicy { } } + private boolean ieEligibleForRoundedCorners(@NonNull WindowState mainWindow) { + return isLetterboxedNotForDisplayCutout(mainWindow) + && !isFreeformActivityMatchParentAppBoundsHeight(); + } + private boolean isLetterboxedNotForDisplayCutout(@NonNull WindowState mainWindow) { return shouldShowLetterboxUi(mainWindow) && !mainWindow.isLetterboxedForDisplayCutout(); } + private boolean isFreeformActivityMatchParentAppBoundsHeight() { + if (!EXCLUDE_CAPTION_FROM_APP_BOUNDS.isTrue()) { + return false; + } + final Task task = mActivityRecord.getTask(); + if (task == null) { + return false; + } + final Rect parentAppBounds = task.getWindowConfiguration().getAppBounds(); + if (parentAppBounds == null) { + return false; + } + + mLetterboxPolicyState.getLetterboxInnerBounds(mTmpRect); + final int diff = parentAppBounds.height() - mTmpRect.height(); + // Compare bounds with tolerance of 1 px to account for rounding error calculations. + return task.getWindowingMode() == WINDOWING_MODE_FREEFORM && diff <= DIFF_TOLERANCE_PX; + } + private static boolean shouldNotLayoutLetterbox(@Nullable WindowState w) { if (w == null) { return true; } - final int type = w.mAttrs.type; + final int type = w.getAttrs().type; // Allow letterbox to be displayed early for base application or application starting // windows even if it is not on the top z order to prevent flickering when the // letterboxed window is brought to the top diff --git a/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java b/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java index 92d76e5616d8..8165638d4bda 100644 --- a/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java +++ b/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java @@ -35,12 +35,12 @@ class AppCompatRoundedCorners { @NonNull private final ActivityRecord mActivityRecord; @NonNull - private final Predicate<WindowState> mIsLetterboxedNotForDisplayCutout; + private final Predicate<WindowState> mRoundedCornersWindowCondition; AppCompatRoundedCorners(@NonNull ActivityRecord activityRecord, - @NonNull Predicate<WindowState> isLetterboxedNotForDisplayCutout) { + @NonNull Predicate<WindowState> roundedCornersWindowCondition) { mActivityRecord = activityRecord; - mIsLetterboxedNotForDisplayCutout = isLetterboxedNotForDisplayCutout; + mRoundedCornersWindowCondition = roundedCornersWindowCondition; } void updateRoundedCornersIfNeeded(@NonNull final WindowState mainWindow) { @@ -136,7 +136,7 @@ class AppCompatRoundedCorners { private boolean requiresRoundedCorners(@NonNull final WindowState mainWindow) { final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord .mAppCompatController.getLetterboxOverrides(); - return mIsLetterboxedNotForDisplayCutout.test(mainWindow) + return mRoundedCornersWindowCondition.test(mainWindow) && letterboxOverrides.isLetterboxActivityCornersRounded(); } diff --git a/services/core/java/com/android/server/wm/AppCompatSandboxingPolicy.java b/services/core/java/com/android/server/wm/AppCompatSandboxingPolicy.java new file mode 100644 index 000000000000..6a46f57e2640 --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatSandboxingPolicy.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2025 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.DesktopModeFlags.EXCLUDE_CAPTION_FROM_APP_BOUNDS; + +import static com.android.server.wm.AppCompatUtils.isInDesktopMode; + +import android.annotation.NonNull; +import android.app.WindowConfiguration.WindowingMode; +import android.content.res.Configuration; +import android.graphics.Rect; + +/** + * Encapsulate logic related to sandboxing for app compatibility. + */ +class AppCompatSandboxingPolicy { + + @NonNull + private final ActivityRecord mActivityRecord; + + AppCompatSandboxingPolicy(@NonNull ActivityRecord activityRecord) { + mActivityRecord = activityRecord; + } + + /** + * In freeform, the container bounds are scaled with app bounds. Activity bounds can be + * outside of its container bounds if insets are coupled with configuration outside of + * freeform and maintained in freeform for size compat mode. + * + * <p>Sandbox activity bounds in freeform to app bounds to force app to display within the + * container. This prevents UI cropping when activities can draw below insets which are + * normally excluded from appBounds before targetSDK < 35 + * (see ConfigurationContainer#applySizeOverrideIfNeeded). + */ + void sandboxBoundsIfNeeded(@NonNull Configuration resolvedConfig, + @WindowingMode int windowingMode) { + if (!EXCLUDE_CAPTION_FROM_APP_BOUNDS.isTrue()) { + return; + } + + if (isInDesktopMode(mActivityRecord.mAtmService.mContext, windowingMode)) { + Rect appBounds = resolvedConfig.windowConfiguration.getAppBounds(); + if (appBounds == null || appBounds.isEmpty()) { + // When there is no override bounds, the activity will inherit the bounds from + // parent. + appBounds = mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride; + } + resolvedConfig.windowConfiguration.setBounds(appBounds); + } + } +} diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java index bbc33004ee54..2cfa242bc5fe 100644 --- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java @@ -17,14 +17,13 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_METADATA; import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_OVERRIDE; import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_METADATA; import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_OVERRIDE; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; -import static com.android.server.wm.DesktopModeHelper.canEnterDesktopMode; +import static com.android.server.wm.AppCompatUtils.isInDesktopMode; import android.annotation.NonNull; import android.annotation.Nullable; @@ -545,9 +544,8 @@ class AppCompatSizeCompatModePolicy { // Allow an application to be up-scaled if its window is smaller than its // original container or if it's a freeform window in desktop mode. boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH) - || (canEnterDesktopMode(mActivityRecord.mAtmService.mContext) - && newParentConfig.windowConfiguration.getWindowingMode() - == WINDOWING_MODE_FREEFORM); + || isInDesktopMode(mActivityRecord.mAtmService.mContext, + newParentConfig.windowConfiguration.getWindowingMode()); return shouldAllowUpscaling ? Math.min( (float) viewportW / contentW, (float) viewportH / contentH) : 1f; } diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index 3e054fc40540..146044008b3f 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -16,16 +16,20 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.content.res.Configuration.UI_MODE_TYPE_MASK; import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET; import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.DesktopModeHelper.canEnterDesktopMode; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppCompatTaskInfo; import android.app.CameraCompatTaskInfo; import android.app.TaskInfo; +import android.app.WindowConfiguration.WindowingMode; +import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; import android.view.InsetsSource; @@ -276,6 +280,14 @@ final class AppCompatUtils { inOutConfig.windowConfiguration.getAppBounds().offset(offsetX, offsetY); } + /** + * Return {@code true} if window is currently in desktop mode. + */ + static boolean isInDesktopMode(@NonNull Context context, + @WindowingMode int parentWindowingMode) { + return parentWindowingMode == WINDOWING_MODE_FREEFORM && canEnterDesktopMode(context); + } + private static void clearAppCompatTaskInfo(@NonNull AppCompatTaskInfo info) { info.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET; info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET; diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index d98ad8bb9e05..12d4a210400c 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -89,7 +89,6 @@ import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpe import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation; import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM; -import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM; import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE; import static com.android.server.wm.AppTransitionProto.LAST_USED_APP_TRANSITION; @@ -1554,26 +1553,6 @@ public class AppTransition implements Dump { } private void handleAppTransitionTimeout() { - synchronized (mService.mGlobalLock) { - final DisplayContent dc = mDisplayContent; - if (dc == null) { - return; - } - notifyAppTransitionTimeoutLocked(); - if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty() - || !dc.mChangingContainers.isEmpty()) { - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, - "*** APP TRANSITION TIMEOUT. displayId=%d isTransitionSet()=%b " - + "mOpeningApps.size()=%d mClosingApps.size()=%d " - + "mChangingApps.size()=%d", - dc.getDisplayId(), dc.mAppTransition.isTransitionSet(), - dc.mOpeningApps.size(), dc.mClosingApps.size(), - dc.mChangingContainers.size()); - - setTimeout(); - mService.mWindowPlacerLocked.performSurfacePlacement(); - } - } } private static void doAnimationCallback(@NonNull IRemoteCallback callback) { diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index e76a83453a9d..d652ea1e26a4 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -190,7 +190,9 @@ class BackNavigationController { currentActivity = window.mActivityRecord; currentTask = window.getTask(); if ((currentTask != null && !currentTask.isVisibleRequested()) - || (currentActivity != null && !currentActivity.isVisibleRequested())) { + || (currentActivity != null && !currentActivity.isVisibleRequested()) + || (currentActivity != null && currentTask != null + && currentTask.getTopNonFinishingActivity() != currentActivity)) { // Closing transition is happening on focus window and should be update soon, // don't drive back navigation with it. ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Focus window is closing."); @@ -279,6 +281,10 @@ class BackNavigationController { } else if (hasTranslucentActivity(currentActivity, prevActivities)) { // skip if one of participant activity is translucent backType = BackNavigationInfo.TYPE_CALLBACK; + } else if (!allActivitiesHaveProcesses(prevActivities)) { + // Skip if one of previous activity has no process. Restart process can be slow, and + // the final hierarchy could be different. + backType = BackNavigationInfo.TYPE_CALLBACK; } else if (prevActivities.size() > 0 && requestOverride == SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED) { if ((!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities)) @@ -601,6 +607,17 @@ class BackNavigationController { return false; } + private static boolean allActivitiesHaveProcesses( + @NonNull ArrayList<ActivityRecord> prevActivities) { + for (int i = prevActivities.size() - 1; i >= 0; --i) { + final ActivityRecord test = prevActivities.get(i); + if (!test.hasProcess()) { + return false; + } + } + return true; + } + private static boolean isAllActivitiesCanShowWhenLocked( @NonNull ArrayList<ActivityRecord> prevActivities) { for (int i = prevActivities.size() - 1; i >= 0; --i) { diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 05dcbb7f9af4..aaa18ad6acc9 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -257,10 +257,12 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { // This should be the only place override the configuration for ActivityRecord. Override // the value if not calculated yet. Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + Rect outConfigBounds = new Rect(outAppBounds); if (outAppBounds == null || outAppBounds.isEmpty()) { inOutConfig.windowConfiguration.setAppBounds( newParentConfiguration.windowConfiguration.getBounds()); outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + outConfigBounds.set(outAppBounds); if (task != null) { task = task.getCreatedByOrganizerTask(); if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) { @@ -279,6 +281,12 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { outAppBounds.inset(decor.mOverrideNonDecorInsets); } } + if (!outConfigBounds.intersect(decor.mOverrideConfigFrame)) { + if (inOutConfig.windowConfiguration.getWindowingMode() + == WINDOWING_MODE_MULTI_WINDOW) { + outAppBounds.inset(decor.mOverrideConfigInsets); + } + } if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) { outAppBounds.offset(-task.mOffsetXForInsets, -task.mOffsetYForInsets); } @@ -289,10 +297,10 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { - inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f); + inOutConfig.screenWidthDp = (int) (outConfigBounds.width() / density + 0.5f); } if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f); + inOutConfig.screenHeightDp = (int) (outConfigBounds.height() / density + 0.5f); } if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) { diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java index 4eaa11bac016..f473b7b7e4fb 100644 --- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java +++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java @@ -60,10 +60,11 @@ class DeferredDisplayUpdater { */ @VisibleForTesting static final DisplayInfoFieldsUpdater DEFERRABLE_FIELDS = (out, override) -> { - // Treat unique id and address change as WM-specific display change as we re-query display - // settings and parameters based on it which could cause window changes + // Treat unique id, address, and canHostTasks change as WM-specific display change as we + // re-query display settings and parameters based on it which could cause window changes. out.uniqueId = override.uniqueId; out.address = override.address; + out.canHostTasks = override.canHostTasks; // Also apply WM-override fields, since they might produce differences in window hierarchy WM_OVERRIDE_FIELDS.setFields(out, override); @@ -433,7 +434,7 @@ class DeferredDisplayUpdater { second.thermalRefreshRateThrottling) || !Objects.equals(first.thermalBrightnessThrottlingDataId, second.thermalBrightnessThrottlingDataId) - || first.canHostTasks != second.canHostTasks) { + ) { diff |= DIFF_NOT_WM_DEFERRABLE; } @@ -454,6 +455,7 @@ class DeferredDisplayUpdater { || !Objects.equals(first.displayShape, second.displayShape) || !Objects.equals(first.uniqueId, second.uniqueId) || !Objects.equals(first.address, second.address) + || first.canHostTasks != second.canHostTasks ) { diff |= DIFF_WM_DEFERRABLE; } diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java index f35930700653..c2255d8d011a 100644 --- a/services/core/java/com/android/server/wm/DesktopModeHelper.java +++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java @@ -51,13 +51,8 @@ public final class DesktopModeHelper { } /** - * Return {@code true} if the current device can hosts desktop sessions on its internal display. + * Return {@code true} if the current device supports desktop mode. */ - @VisibleForTesting - static boolean canInternalDisplayHostDesktops(@NonNull Context context) { - return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops); - } - // TODO(b/337819319): use a companion object instead. private static boolean isDesktopModeSupported(@NonNull Context context) { return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported); @@ -68,32 +63,45 @@ public final class DesktopModeHelper { } /** + * Return {@code true} if the current device can hosts desktop sessions on its internal display. + */ + @VisibleForTesting + static boolean canInternalDisplayHostDesktops(@NonNull Context context) { + return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops); + } + + /** * Check if Desktop mode should be enabled because the dev option is shown and enabled. */ private static boolean isDesktopModeEnabledByDevOption(@NonNull Context context) { return DesktopModeFlags.isDesktopModeForcedEnabled() && (isDesktopModeDevOptionsSupported( - context) || isInternalDisplayEligibleToHostDesktops(context)); + context) || isDeviceEligibleForDesktopMode(context)); } @VisibleForTesting - static boolean isInternalDisplayEligibleToHostDesktops(@NonNull Context context) { - return !shouldEnforceDeviceRestrictions() || canInternalDisplayHostDesktops(context) || ( - Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionsSupported( - context)); + static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) { + if (!shouldEnforceDeviceRestrictions()) { + return true; + } + final boolean desktopModeSupported = isDesktopModeSupported(context) + && canInternalDisplayHostDesktops(context); + final boolean desktopModeSupportedByDevOptions = + Flags.enableDesktopModeThroughDevOption() + && isDesktopModeDevOptionsSupported(context); + return desktopModeSupported || desktopModeSupportedByDevOptions; } /** * Return {@code true} if desktop mode can be entered on the current device. */ static boolean canEnterDesktopMode(@NonNull Context context) { - return (isInternalDisplayEligibleToHostDesktops(context) - && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue() - && (isDesktopModeSupported(context) || !shouldEnforceDeviceRestrictions())) + return (isDeviceEligibleForDesktopMode(context) + && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue()) || isDesktopModeEnabledByDevOption(context); } /** Returns {@code true} if desktop experience wallpaper is supported on this device. */ public static boolean isDeviceEligibleForDesktopExperienceWallpaper(@NonNull Context context) { - return enableConnectedDisplaysWallpaper() && canEnterDesktopMode(context); + return enableConnectedDisplaysWallpaper() && isDeviceEligibleForDesktopMode(context); } } diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java index ac987929a142..b6f74a08631e 100644 --- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java @@ -75,8 +75,8 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { return RESULT_SKIP; } if (com.android.window.flags.Flags.fixLayoutExistingTask() - && task.getOrganizedTask() != null) { - appendLog("task is organized, skipping"); + && task.getCreatedByOrganizerTask() != null) { + appendLog("has created-by-organizer-task, skipping"); return RESULT_SKIP; } @@ -111,6 +111,11 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { return RESULT_SKIP; } + if ((options == null || options.getLaunchBounds() == null) && task.hasOverrideBounds()) { + appendLog("current task has bounds set, not overriding"); + return RESULT_SKIP; + } + DesktopModeBoundsCalculator.updateInitialBounds(task, layout, activity, options, outParams.mBounds, this::appendLog); appendLog("final desktop mode task bounds set to %s", outParams.mBounds); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 682f3d8cf1e5..e30c24d87d20 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -108,7 +108,6 @@ import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_W import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY; 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; import static com.android.server.wm.DisplayContentProto.DISPLAY_FRAMES; import static com.android.server.wm.DisplayContentProto.DISPLAY_INFO; @@ -125,7 +124,6 @@ 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.KEEP_CLEAR_AREAS; import static com.android.server.wm.DisplayContentProto.MIN_SIZE_OF_RESIZEABLE_TASK_DP; -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.SLEEP_TOKENS; @@ -164,6 +162,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.content.ComponentCallbacks; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; @@ -196,7 +195,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.provider.Settings; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.DisplayUtils; @@ -367,15 +365,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final AppTransition mAppTransition; - final ArraySet<ActivityRecord> mOpeningApps = new ArraySet<>(); - final ArraySet<ActivityRecord> mClosingApps = new ArraySet<>(); - final ArraySet<WindowContainer> mChangingContainers = new ArraySet<>(); final UnknownAppVisibilityController mUnknownAppVisibilityController; - /** - * If a container is closing when resizing, keeps track of its starting bounds when it is - * removed from {@link #mChangingContainers}. - */ - final ArrayMap<WindowContainer, Rect> mClosingChangingContainers = new ArrayMap<>(); private MetricsLogger mMetricsLogger; @@ -467,6 +457,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp private DisplayInfo mLastDisplayInfoOverride; private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + @NonNull private final DisplayPolicy mDisplayPolicy; private final DisplayRotation mDisplayRotation; @@ -553,6 +544,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Remove this display when animation on it has completed. */ private boolean mDeferredRemoval; + @NonNull final PinnedTaskController mPinnedTaskController; private final LinkedList<ActivityRecord> mTmpUpdateAllDrawn = new LinkedList(); @@ -1113,6 +1105,29 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp }; /** + * Called to update fields retrieve from {@link #getDisplayUiContext()} resources when + * there's a configuration update on {@link #getDisplayUiContext()}. + */ + @NonNull + private final ComponentCallbacks mSysUiContextConfigCallback = new ComponentCallbacks() { + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + synchronized (mWmService.mGlobalLock) { + if (mDisplayReady) { + mDisplayPolicy.onConfigurationChanged(); + mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp(); + } + } + } + + @Override + public void onLowMemory() { + // Do nothing. + } + }; + + /** * Create new {@link DisplayContent} instance, add itself to the root window container and * initialize direct children. * @param display May not be null. @@ -1910,17 +1925,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return false; } if (checkOpening) { - if (mTransitionController.isShellTransitionsEnabled()) { - if (!mTransitionController.isCollecting(r)) { - return false; - } - } else { - if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) { - // Apply normal rotation animation in case of the activity set different - // requested orientation without activity switch, or the transition is unset due - // to starting window was transferred ({@link #mSkipAppTransitionAnimation}). - return false; - } + if (!mTransitionController.isCollecting(r)) { + // Apply normal rotation animation in case the activity changes requested + // orientation without activity switch. + return false; } if (r.isState(RESUMED) && !r.getTask().mInResumeTopActivity) { // If the activity is executing or has done the lifecycle callback, use normal @@ -2815,11 +2823,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final int lastOrientation = getConfiguration().orientation; final int lastWindowingMode = getWindowingMode(); super.onConfigurationChanged(newParentConfig); - if (mDisplayPolicy != null) { - mDisplayPolicy.onConfigurationChanged(); - mPinnedTaskController.onPostDisplayConfigurationChanged(); - mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp(); + if (!Flags.trackSystemUiContextBeforeWms()) { + mSysUiContextConfigCallback.onConfigurationChanged(newParentConfig); } + mPinnedTaskController.onPostDisplayConfigurationChanged(); // Update IME parent if needed. updateImeParent(); @@ -3239,25 +3246,48 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp Slog.e(TAG, "ShouldShowSystemDecors shouldn't be updated when the flag is off."); } - final boolean shouldShow; - if (isDefaultDisplay) { - shouldShow = true; - } else if (isPrivate()) { - shouldShow = false; - } else { - shouldShow = mDisplay.canHostTasks(); + final boolean shouldShowContent; + if (!allowContentModeSwitch()) { + return; } + shouldShowContent = mDisplay.canHostTasks(); - if (shouldShow == mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)) { + if (shouldShowContent == mWmService.mDisplayWindowSettings + .shouldShowSystemDecorsLocked(this)) { return; } - mWmService.mDisplayWindowSettings.setShouldShowSystemDecorsLocked(this, shouldShow); + mWmService.mDisplayWindowSettings.setShouldShowSystemDecorsLocked(this, shouldShowContent); - if (!shouldShow) { + if (!shouldShowContent) { clearAllTasksOnDisplay(null /* clearTasksCallback */, false /* isRemovingDisplay */); } } + /** + * Note that we only allow displays that are able to show system decorations to use the content + * mode switch; however, not all displays that are able to show system decorations are allowed + * to use the content mode switch. + */ + boolean allowContentModeSwitch() { + // The default display should always show system decorations. + if (isDefaultDisplay) { + return false; + } + + // Private display should never show system decorations. + if (isPrivate()) { + return false; + } + + // TODO(b/391965805): Remove this after introducing FLAG_ALLOW_SYSTEM_DECORATIONS_CHANGE. + // Virtual displays cannot add or remove system decorations during their lifecycle. + if (mDisplay.getType() == Display.TYPE_VIRTUAL) { + return false; + } + + return true; + } + DisplayCutout loadDisplayCutout(int displayWidth, int displayHeight) { if (mDisplayPolicy == null || mInitialDisplayCutout == null) { return null; @@ -3354,10 +3384,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void removeImmediately() { mDeferredRemoval = false; try { - // Clear all transitions & screen frozen states when removing display. - mOpeningApps.clear(); - mClosingApps.clear(); - mChangingContainers.clear(); mUnknownAppVisibilityController.clear(); mAppTransition.removeAppTransitionTimeoutCallbacks(); mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener); @@ -3380,6 +3406,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .getKeyguardController().onDisplayRemoved(mDisplayId); mWallpaperController.resetLargestDisplay(mDisplay); mWmService.mDisplayWindowSettings.onDisplayRemoved(this); + if (Flags.trackSystemUiContextBeforeWms()) { + getDisplayUiContext().unregisterComponentCallbacks(mSysUiContextConfigCallback); + } } finally { mDisplayReady = false; } @@ -3549,12 +3578,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (mFocusedApp != null) { mFocusedApp.writeNameToProto(proto, FOCUSED_APP); } - for (int i = mOpeningApps.size() - 1; i >= 0; i--) { - mOpeningApps.valueAt(i).writeIdentifierToProto(proto, OPENING_APPS); - } - for (int i = mClosingApps.size() - 1; i >= 0; i--) { - mClosingApps.valueAt(i).writeIdentifierToProto(proto, CLOSING_APPS); - } final Task focusedRootTask = getFocusedRootTask(); if (focusedRootTask != null) { @@ -3822,7 +3845,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (mTmpWindow == null) { ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: No focusable windows, display=%d", getDisplayId()); - return null; } return mTmpWindow; } @@ -4813,19 +4835,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } - if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty() || !mChangingContainers.isEmpty()) { - pw.println(); - if (mOpeningApps.size() > 0) { - pw.print(" mOpeningApps="); pw.println(mOpeningApps); - } - if (mClosingApps.size() > 0) { - pw.print(" mClosingApps="); pw.println(mClosingApps); - } - if (mChangingContainers.size() > 0) { - pw.print(" mChangingApps="); pw.println(mChangingContainers); - } - } - mUnknownAppVisibilityController.dump(pw, " "); } @@ -5447,7 +5456,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp reconfigureDisplayLocked(); onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); mWmService.mDisplayNotificationController.dispatchDisplayAdded(this); - // Attach the SystemUiContext to this DisplayContent the get latest configuration. + // Attach the SystemUiContext to this DisplayContent to get latest configuration. // Note that the SystemUiContext will be removed automatically if this DisplayContent // is detached. registerSystemUiContext(); @@ -5455,11 +5464,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } private void registerSystemUiContext() { + final Context systemUiContext = getDisplayUiContext(); final WindowProcessController wpc = mAtmService.getProcessController( - getDisplayUiContext().getIApplicationThread()); + systemUiContext.getIApplicationThread()); mWmService.mWindowContextListenerController.registerWindowContainerListener( - wpc, getDisplayUiContext().getWindowContextToken(), this, + wpc, systemUiContext.getWindowContextToken(), this, INVALID_WINDOW_TYPE, null /* options */); + if (Flags.trackSystemUiContextBeforeWms()) { + systemUiContext.registerComponentCallbacks(mSysUiContextConfigCallback); + } } @Override @@ -6578,22 +6591,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .getKeyguardController().isKeyguardLocked(mDisplayId); } - boolean isKeyguardLockedOrAodShowing() { - return isKeyguardLocked() || isAodShowing(); - } - - /** - * @return whether aod is showing for this display - */ - boolean isAodShowing() { - final boolean isAodShowing = mRootWindowContainer.mTaskSupervisor - .getKeyguardController().isAodShowing(mDisplayId); - if (mDisplayId == DEFAULT_DISPLAY && isAodShowing) { - return !isKeyguardGoingAway(); - } - return isAodShowing; - } - /** * @return whether keyguard is going away on this display */ @@ -6654,6 +6651,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp forAllTasks((t) -> { t.getRootTask().removeChild(t, "removeAllTasks"); }); } + @NonNull Context getDisplayUiContext() { return mDisplayPolicy.getSystemUiContext(); } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 10f591cfd379..fbe850198c50 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1865,6 +1865,7 @@ public class DisplayPolicy { return mContext; } + @NonNull Context getSystemUiContext() { return mUiContext; } diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java index 539fc123720e..117387553f30 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java +++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java @@ -247,12 +247,7 @@ class DisplayWindowSettings { void setShouldShowSystemDecorsLocked(@NonNull DisplayContent dc, boolean shouldShow) { final boolean changed = (shouldShow != shouldShowSystemDecorsLocked(dc)); - - final DisplayInfo displayInfo = dc.getDisplayInfo(); - final SettingsProvider.SettingsEntry overrideSettings = - mSettingsProvider.getOverrideSettings(displayInfo); - overrideSettings.mShouldShowSystemDecors = shouldShow; - mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings); + setShouldShowSystemDecorsInternalLocked(dc, shouldShow); if (enableDisplayContentModeManagement()) { if (dc.isDefaultDisplay || dc.isPrivate() || !changed) { @@ -269,6 +264,15 @@ class DisplayWindowSettings { } } + void setShouldShowSystemDecorsInternalLocked(@NonNull DisplayContent dc, + boolean shouldShow) { + final DisplayInfo displayInfo = dc.getDisplayInfo(); + final SettingsProvider.SettingsEntry overrideSettings = + mSettingsProvider.getOverrideSettings(displayInfo); + overrideSettings.mShouldShowSystemDecors = shouldShow; + mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings); + } + boolean isHomeSupportedLocked(@NonNull DisplayContent dc) { if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) { // Default display should show home. diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 69f32cb7b8ea..84281b8fbecf 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -122,7 +122,7 @@ class DragState { float mThumbOffsetX, mThumbOffsetY; InputInterceptor mInputInterceptor; ArrayList<WindowState> mNotifiedWindows; - boolean mDragInProgress; + private boolean mDragInProgress; // Set to non -1 value if a valid app requests DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START int mCallingTaskIdToHide; /** @@ -161,7 +161,7 @@ class DragState { private boolean mIsClosing; // Stores the last drop event which was reported to a valid drop target window, or null - // otherwise. This drop event will contain private info and should only be consumed by the + // otherwise. This drop event will contain private info and should only be consumed by the // unhandled drag listener. DragEvent mUnhandledDropEvent; @@ -243,7 +243,7 @@ class DragState { for (WindowState ws : mNotifiedWindows) { float inWindowX = 0; float inWindowY = 0; - SurfaceControl dragSurface = null; + boolean includeDragSurface = false; if (!mDragResult && (ws.mSession.mPid == mPid)) { // Report unconsumed drop location back to the app that started the drag. inWindowX = ws.translateToWindowX(mCurrentDisplayX); @@ -251,13 +251,10 @@ class DragState { if (relinquishDragSurfaceToDragSource()) { // If requested (and allowed), report the drag surface back to the app // starting the drag to handle the return animation - dragSurface = mSurfaceControl; + includeDragSurface = true; } } - DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, inWindowX, - inWindowY, mThumbOffsetX, mThumbOffsetY, - mCurrentDisplayContent.getDisplayId(), mFlags, null, null, null, - dragSurface, null, mDragResult); + DragEvent event = obtainDragEndedEvent(inWindowX, inWindowY, includeDragSurface); try { if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DRAG_ENDED to " + ws); ws.mClient.dispatchDragEvent(event); @@ -310,10 +307,10 @@ class DragState { /** * Creates the drop event for dispatching to the unhandled drag. - * TODO(b/384841906): Update `inWindowX` and `inWindowY` to be display-coordinate. */ - private DragEvent createUnhandledDropEvent(float inWindowX, float inWindowY) { - return obtainDragEvent(DragEvent.ACTION_DROP, inWindowX, inWindowY, mDataDescription, mData, + private DragEvent createUnhandledDropEvent(float inDisplayX, float inDisplayY) { + return obtainDragEvent(DragEvent.ACTION_DROP, inDisplayX, inDisplayY, mDataDescription, + mData, /* includeDragSurface= */ true, /* includeDragFlags= */ true, null /* dragAndDropPermissions */); } @@ -370,11 +367,8 @@ class DragState { } final WindowState touchedWin = mService.mInputToWindowMap.get(token); - // TODO(b/384841906): The x, y here when sent to a window and unhandled, will still be - // relative to the window it was originally sent to. Need to update this to actually be - // display-coordinate. - final DragEvent unhandledDropEvent = createUnhandledDropEvent(inWindowX, inWindowY); if (!isWindowNotified(touchedWin)) { + final DragEvent unhandledDropEvent = createUnhandledDropEvent(inWindowX, inWindowY); // Delegate to the unhandled drag listener as a first pass if (mDragDropController.notifyUnhandledDrop(unhandledDropEvent, "unhandled-drop")) { // The unhandled drag listener will call back to notify whether it has consumed @@ -392,6 +386,8 @@ class DragState { } if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to " + touchedWin); + final DragEvent unhandledDropEvent = createUnhandledDropEvent( + touchedWin.getBounds().left + inWindowX, touchedWin.getBounds().top + inWindowY); final IBinder clientToken = touchedWin.mClient.asBinder(); final DragEvent event = createDropEvent(inWindowX, inWindowY, touchedWin); @@ -776,28 +772,37 @@ class DragState { displayId, (int) (displayX - mThumbOffsetX), (int) (displayY - mThumbOffsetY)); } - /** - * Returns true if it has sent DRAG_STARTED broadcast out but has not been sent DRAG_END - * broadcast. - */ - boolean isInProgress() { - return mDragInProgress; + private DragEvent obtainDragEndedEvent(float x, float y, boolean includeDragSurface) { + return obtainDragEvent(DragEvent.ACTION_DRAG_ENDED, x, y, /* description= */ + null, /* data= */ null, includeDragSurface, /* includeDragFlags= */ + true, /* dragAndDropPermissions= */ null, mDragResult); + } + + private DragEvent obtainDragEvent(int action, float x, float y, ClipDescription description, + ClipData data, boolean includeDragSurface, boolean includeDragFlags, + IDragAndDropPermissions dragAndDropPermissions) { + return obtainDragEvent(action, x, y, description, data, includeDragSurface, + includeDragFlags, dragAndDropPermissions, /* dragResult= */ false); } /** * `x` and `y` here varies between local window coordinate, relative coordinate to another * window and local display coordinate, all depending on the `action`. Please take a look * at the callers to determine the type. - * TODO(b/384845022): Properly document the events sent based on the event type. + * - ACTION_DRAG_STARTED: (x, y) is relative coordinate to the target window's origin + * (possible to have negative values). + * - ACTION_DROP: + * --- UnhandledDropEvent: (x, y) is in display space coordinate. + * --- DropEvent: (x, y) is in local window coordinate where event is targeted to. + * - ACTION_DRAG_ENDED: (x, y) is in local window coordinate where event is targeted to. */ private DragEvent obtainDragEvent(int action, float x, float y, ClipDescription description, ClipData data, boolean includeDragSurface, boolean includeDragFlags, - IDragAndDropPermissions dragAndDropPermissions) { + IDragAndDropPermissions dragAndDropPermissions, boolean dragResult) { return DragEvent.obtain(action, x, y, mThumbOffsetX, mThumbOffsetY, mCurrentDisplayContent.getDisplayId(), includeDragFlags ? mFlags : 0, null /* localState */, description, data, - includeDragSurface ? mSurfaceControl : null, dragAndDropPermissions, - false /* result */); + includeDragSurface ? mSurfaceControl : null, dragAndDropPermissions, dragResult); } private ValueAnimator createReturnAnimationLocked() { diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index dd2f49e171a8..6091b8334438 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -18,7 +18,6 @@ package com.android.server.wm; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; @@ -217,9 +216,6 @@ class KeyguardController { } else if (keyguardShowing && !state.mKeyguardShowing) { transition.addFlag(TRANSIT_FLAG_KEYGUARD_APPEARING); } - if (mWindowManager.mFlags.mAodTransition && aodShowing && !state.mAodShowing) { - transition.addFlag(TRANSIT_FLAG_AOD_APPEARING); - } } } // Update the task snapshot if the screen will not be turned off. To make sure that the @@ -242,27 +238,19 @@ class KeyguardController { state.mAodShowing = aodShowing; state.writeEventLog("setKeyguardShown"); - if (keyguardChanged || aodChanged) { - if (keyguardChanged) { - // Irrelevant to AOD. - state.mKeyguardGoingAway = false; - if (keyguardShowing) { - state.mDismissalRequested = false; - } + if (keyguardChanged) { + // Irrelevant to AOD. + state.mKeyguardGoingAway = false; + if (keyguardShowing) { + state.mDismissalRequested = false; } if (goingAwayRemoved - || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state)) - || (mWindowManager.mFlags.mAodTransition && aodShowing)) { + || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))) { // Keyguard decided to show or stopped going away. Send a transition to animate back // to the locked state before holding the sleep token again if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { dc.requestTransitionAndLegacyPrepare( TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING); - if (mWindowManager.mFlags.mAodTransition && aodShowing - && dc.mTransitionController.isCollecting()) { - dc.mTransitionController.getCollectingTransition().addFlag( - TRANSIT_FLAG_AOD_APPEARING); - } } dc.mWallpaperController.adjustWallpaperWindows(); dc.executeAppTransition(); diff --git a/services/core/java/com/android/server/wm/PresentationController.java b/services/core/java/com/android/server/wm/PresentationController.java index 69463433827f..b3cff9c6cc3d 100644 --- a/services/core/java/com/android/server/wm/PresentationController.java +++ b/services/core/java/com/android/server/wm/PresentationController.java @@ -56,11 +56,6 @@ class PresentationController { ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Presentation added to display %d: %s", win.getDisplayId(), win); mPresentingDisplayIds.add(win.getDisplayId()); - if (enablePresentationForConnectedDisplays()) { - // A presentation hides all activities behind on the same display. - win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null, - /*notifyClients=*/ true); - } win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ true); } @@ -76,11 +71,6 @@ class PresentationController { if (displayIdIndex != -1) { mPresentingDisplayIds.remove(displayIdIndex); } - if (enablePresentationForConnectedDisplays()) { - // A presentation hides all activities behind on the same display. - win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null, - /*notifyClients=*/ true); - } win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ false); } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index c93efd327096..40f16c187f20 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2775,6 +2775,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return; } + if (enableDisplayContentModeManagement() && display.allowContentModeSwitch()) { + mWindowManager.mDisplayWindowSettings + .setShouldShowSystemDecorsInternalLocked(display, + display.mDisplay.canHostTasks()); + } + startSystemDecorations(display, "displayAdded"); // Drop any cached DisplayInfos associated with this display id - the values are now diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 3abab8bf62c2..988af44f6e55 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -166,6 +166,7 @@ import android.view.InsetsState; import android.view.SurfaceControl; import android.view.WindowInsets; import android.view.WindowManager; +import android.window.DesktopExperienceFlags; import android.window.DesktopModeFlags; import android.window.ITaskOrganizer; import android.window.PictureInPictureSurfaceTransaction; @@ -503,6 +504,17 @@ class Task extends TaskFragment { int mOffsetYForInsets; /** + * When set to true, the task will be kept at a PERCEPTIBLE_APP_ADJ, and downgraded + * to PREVIOUS_APP_ADJ if not in foreground for a period of time. + * One example use case is for desktop form factors, where it is important keep tasks in the + * perceptible state (rather than cached where it may be frozen) when a user moves it to the + * foreground. + * On startup, restored Tasks will not be perceptible, until user actually interacts with it + * (i.e. brings it to the foreground) + */ + boolean mIsPerceptible = false; + + /** * Whether the compatibility overrides that change the resizability of the app should be allowed * for the specific app. */ @@ -2027,7 +2039,7 @@ class Task extends TaskFragment { } if (shouldStartChangeTransition(prevWinMode, mTmpPrevBounds)) { - initializeChangeTransition(mTmpPrevBounds); + mTransitionController.collectVisibleChange(this); } // If the configuration supports persistent bounds (eg. Freeform), keep track of the @@ -2378,7 +2390,7 @@ class Task extends TaskFragment { // configurations and let its parent (organized task) to control it; final Task rootTask = getRootTask(); boolean shouldInheritBounds = rootTask != this && rootTask.isOrganized(); - if (Flags.enableMultipleDesktopsBackend()) { + if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) { // Only inherit from organized parent when this task is not organized. shouldInheritBounds &= !isOrganized(); } @@ -3853,6 +3865,7 @@ class Task extends TaskFragment { pw.print(ActivityInfo.resizeModeToString(mResizeMode)); pw.print(" mSupportsPictureInPicture="); pw.print(mSupportsPictureInPicture); pw.print(" isResizeable="); pw.println(isResizeable()); + pw.print(" isPerceptible="); pw.println(mIsPerceptible); pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime); pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)"); pw.print(prefix); pw.println(" isTrimmable=" + mIsTrimmableFromRecents); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 74059c1cc9b1..bbda849262b2 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1910,7 +1910,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (!hasDirectChildActivities()) { return false; } - if (mResumedActivity != null && mTransitionController.isTransientLaunch(mResumedActivity)) { + if (mResumedActivity != null && !mResumedActivity.finishing + && mTransitionController.isTransientLaunch(mResumedActivity)) { // Even if the transient activity is occluded, defer pausing (addToStopping will still // be called) it until the transient transition is done. So the current resuming // activity won't need to wait for additional pause complete. @@ -2779,9 +2780,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { } // If this TaskFragment is closing while resizing, crop to the starting bounds instead. - final Rect bounds = isClosingWhenResizing() - ? mDisplayContent.mClosingChangingContainers.get(this) - : getBounds(); + final Rect bounds = getBounds(); final int width = bounds.width(); final int height = bounds.height(); if (!forceUpdate && width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) { diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index e3a5b66b83fd..6c7d979dc43d 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -384,7 +384,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // an existing task. adjustBoundsToAvoidConflictInDisplayArea(taskDisplayArea, outParams.mBounds); } - } else { + } else if (task == null || !task.hasOverrideBounds()) { if (source != null && source.inFreeformWindowingMode() && resolvedMode == WINDOWING_MODE_FREEFORM && outParams.mBounds.isEmpty() diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index fe653e454d6c..5217a759c6ae 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -36,7 +36,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; import static android.view.WindowManager.TRANSIT_OPEN; @@ -974,10 +973,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return false; } - boolean isInAodAppearTransition() { - return (mFlags & TRANSIT_FLAG_AOD_APPEARING) != 0; - } - /** * Specifies configuration change explicitly for the window container, so it can be chosen as * transition target. This is usually used with transition mode diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 25b513d85384..ba7f36419ac5 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -525,19 +525,6 @@ class TransitionController { return false; } - boolean isInAodAppearTransition() { - if (mCollectingTransition != null && mCollectingTransition.isInAodAppearTransition()) { - return true; - } - for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) { - if (mWaitingTransitions.get(i).isInAodAppearTransition()) return true; - } - for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { - if (mPlayingTransitions.get(i).isInAodAppearTransition()) return true; - } - return false; - } - /** * @return A pair of the transition and restore-behind target for the given {@param container}. * @param container An ancestor of a transient-launch activity diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 70948e1264c4..a8b9fedcdc73 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -166,14 +166,6 @@ class WallpaperController { mFindResults.setWallpaperTarget(w); return false; } - } else if (mService.mFlags.mAodTransition - && mDisplayContent.isKeyguardLockedOrAodShowing()) { - if (mService.mPolicy.isKeyguardHostWindow(w.mAttrs) - && w.mTransitionController.isInAodAppearTransition()) { - if (DEBUG_WALLPAPER) Slog.v(TAG, "Found aod transition wallpaper target: " + w); - mFindResults.setWallpaperTarget(w); - return true; - } } final boolean animationWallpaper = animatingContainer != null @@ -300,12 +292,6 @@ class WallpaperController { return false; } - boolean isWallpaperTargetAnimating() { - return mWallpaperTarget != null && mWallpaperTarget.isAnimating(TRANSITION | PARENTS) - && (mWallpaperTarget.mActivityRecord == null - || !mWallpaperTarget.mActivityRecord.isWaitingForTransitionStart()); - } - void hideDeferredWallpapersIfNeededLegacy() { for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) { final WallpaperWindowToken token = mWallpaperTokens.get(i); @@ -692,8 +678,7 @@ class WallpaperController { private WallpaperWindowToken getTokenForTarget(WindowState target) { if (target == null) return null; WindowState window = mFindResults.getTopWallpaper( - (target.canShowWhenLocked() && mService.isKeyguardLocked()) - || (mService.mFlags.mAodTransition && mDisplayContent.isAodShowing())); + target.canShowWhenLocked() && mService.isKeyguardLocked()); return window == null ? null : window.mToken.asWallpaperToken(); } @@ -736,9 +721,7 @@ class WallpaperController { if (mFindResults.wallpaperTarget == null && mFindResults.useTopWallpaperAsTarget) { mFindResults.setWallpaperTarget( - mFindResults.getTopWallpaper(mService.mFlags.mAodTransition - ? mDisplayContent.isKeyguardLockedOrAodShowing() - : mDisplayContent.isKeyguardLocked())); + mFindResults.getTopWallpaper(mDisplayContent.isKeyguardLocked())); } } @@ -848,14 +831,6 @@ class WallpaperController { // 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; - } else if (newTargetHidden == oldTargetHidden - && !mDisplayContent.mOpeningApps.contains(wallpaperTarget.mActivityRecord) - && (mDisplayContent.mOpeningApps.contains(prevWallpaperTarget.mActivityRecord) - || mDisplayContent.mClosingApps.contains(prevWallpaperTarget.mActivityRecord))) { - // If they're both hidden (or both not hidden), prefer the one that's currently in - // opening or closing app list, this allows transition selection logic to better - // determine the wallpaper status of opening/closing apps. - mWallpaperTarget = prevWallpaperTarget; } result.setWallpaperTarget(wallpaperTarget); @@ -910,17 +885,11 @@ class WallpaperController { if (mDisplayContent.mWmService.mFlags.mEnsureWallpaperInTransitions) { visibleRequested = mWallpaperTarget != null && mWallpaperTarget.isVisibleRequested(); } - updateWallpaperTokens(visibleRequested, - mService.mFlags.mAodTransition - ? mDisplayContent.isKeyguardLockedOrAodShowing() - : mDisplayContent.isKeyguardLocked()); + updateWallpaperTokens(visibleRequested, mDisplayContent.isKeyguardLocked()); ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper at display %d - visibility: %b, keyguardLocked: %b", - mDisplayContent.getDisplayId(), visible, - mService.mFlags.mAodTransition - ? mDisplayContent.isKeyguardLockedOrAodShowing() - : mDisplayContent.isKeyguardLocked()); + mDisplayContent.getDisplayId(), visible, mDisplayContent.isKeyguardLocked()); if (visible && mLastFrozen != mFindResults.isWallpaperTargetForLetterbox) { mLastFrozen = mFindResults.isWallpaperTargetForLetterbox; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 95cdf46ea488..45202a29ba97 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -34,7 +34,6 @@ import static android.os.UserHandle.USER_NULL; import static android.view.SurfaceControl.Transaction; import static android.view.WindowInsets.Type.InsetsType; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; -import static android.view.WindowManager.TRANSIT_CHANGE; import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR; import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; @@ -901,10 +900,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< */ @CallSuper void removeImmediately() { - final DisplayContent dc = getDisplayContent(); - if (dc != null) { - dc.mClosingChangingContainers.remove(this); - } while (!mChildren.isEmpty()) { final E child = mChildren.getLast(); child.removeImmediately(); @@ -1116,10 +1111,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< if (asWindowState() == null) { mTransitionController.collect(this); } - // Cancel any change transition queued-up for this container on the old display when - // this container is moved from the old display. - mDisplayContent.mClosingChangingContainers.remove(this); - mDisplayContent.mChangingContainers.remove(this); } mDisplayContent = dc; if (dc != null && dc != this && mPendingTransaction != null) { @@ -1268,14 +1259,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** - * @return {@code true} when the container is waiting the app transition start, {@code false} - * otherwise. - */ - boolean isWaitingForTransitionStart() { - return false; - } - - /** * @return {@code true} if in this subtree of the hierarchy we have an * {@code ActivityRecord#isAnimating(TRANSITION)}, {@code false} otherwise. */ @@ -1302,13 +1285,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return isAnimating(0 /* self only */); } - /** - * @return {@code true} if the container is in changing app transition. - */ - boolean isChangingAppTransition() { - return mDisplayContent != null && mDisplayContent.mChangingContainers.contains(this); - } - boolean inTransition() { return mTransitionController.inTransition(this); } @@ -1427,12 +1403,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return setVisibleRequested(newVisReq); } - /** Whether this window is closing while resizing. */ - boolean isClosingWhenResizing() { - return mDisplayContent != null - && mDisplayContent.mClosingChangingContainers.containsKey(this); - } - void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(HASH_CODE, System.identityHashCode(this)); @@ -3044,36 +3014,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< || (getParent() != null && getParent().inPinnedWindowingMode()); } - /** - * Initializes a change transition. - * - * 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. - */ - void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) { - if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) { - mDisplayContent.mTransitionController.collectVisibleChange(this); - return; - } - 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); - } - - void initializeChangeTransition(Rect startBounds) { - initializeChangeTransition(startBounds, null /* freezeTarget */); - } - ArraySet<WindowContainer> getAnimationSources() { return mSurfaceAnimationSources; } @@ -3166,8 +3106,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< getAnimationPosition(mTmpPoint); mTmpRect.offsetTo(0, 0); - final boolean isChanging = AppTransition.isChangeTransitOld(transit) && enter - && isChangingAppTransition(); + final boolean isChanging = AppTransition.isChangeTransitOld(transit); if (isChanging) { final float durationScale = mWmService.getTransitionAnimationScaleLocked(); @@ -3519,9 +3458,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< && (mSurfaceAnimator.getAnimationType() & typesToCheck) > 0) { return true; } - if ((flags & TRANSITION) != 0 && isWaitingForTransitionStart()) { - return true; - } return false; } @@ -3603,13 +3539,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return; } - if (isClosingWhenResizing()) { - // This container is closing while resizing, keep its surface at the starting position - // to prevent animation flicker. - getRelativePosition(mDisplayContent.mClosingChangingContainers.get(this), mTmpPos); - } else { - getRelativePosition(mTmpPos); - } + getRelativePosition(mTmpPos); final int deltaRotation = getRelativeDisplayRotation(); if (mTmpPos.equals(mLastSurfacePosition) && deltaRotation == mLastDeltaRotation) { return; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index d699a689459e..8aed91b2dc66 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -157,6 +157,7 @@ import static com.android.server.wm.WindowManagerServiceDumpProto.POLICY; import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER; import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID; import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; +import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays; import static com.android.window.flags.Flags.multiCrop; import static com.android.window.flags.Flags.setScPropertiesInClient; @@ -1820,125 +1821,158 @@ public class WindowManagerService extends IWindowManager.Stub final boolean hideSystemAlertWindows = shouldHideNonSystemOverlayWindow(win); win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows); - boolean imMayMove = true; - - win.mToken.addWindow(win); - displayPolicy.addWindowLw(win, attrs); - 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 - // IME window is always touchable. - // Ignore non-touchable windows e.g. Stylus InkWindow.java. - && (win.getAttrs().flags & FLAG_NOT_TOUCHABLE) == 0) { - displayContent.setInputMethodWindowLocked(win); - imMayMove = false; - } else if (type == TYPE_INPUT_METHOD_DIALOG) { - displayContent.computeImeTarget(true /* updateImeTarget */); - imMayMove = false; - } else { - if (type == TYPE_WALLPAPER) { - displayContent.mWallpaperController.clearLastWallpaperTimeoutTime(); - displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; - } else if (win.hasWallpaper()) { - displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; - } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) { - // If there is currently a wallpaper being shown, and - // the base layer of the new window is below the current - // layer of the target window, then adjust the wallpaper. - // This is to avoid a new window being placed between the - // wallpaper and its target. - displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; + // Only a presentation window needs a transition because its visibility affets the + // lifecycle of apps below (b/390481865). + if (enablePresentationForConnectedDisplays() && win.isPresentation()) { + Transition transition = null; + if (!win.mTransitionController.isCollecting()) { + transition = win.mTransitionController.createAndStartCollecting(TRANSIT_OPEN); + } + win.mTransitionController.collect(win.mToken); + res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState, + outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs); + // A presentation hides all activities behind on the same display. + win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null, + /*notifyClients=*/ true); + win.mTransitionController.getCollectingTransition().setReady(win.mToken, true); + if (transition != null) { + win.mTransitionController.requestStartTransition(transition, null, + null /* remoteTransition */, null /* displayChange */); } + } else { + res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState, + outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs); } + } - final WindowStateAnimator winAnimator = win.mWinAnimator; - winAnimator.mEnterAnimationPending = true; - winAnimator.mEnteringAnimation = true; + Binder.restoreCallingIdentity(origId); - if (displayPolicy.areSystemBarsForcedConsumedLw()) { - res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS; - } - if (displayContent.isInTouchMode()) { - res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE; - } - if (win.mActivityRecord == null || win.mActivityRecord.isClientVisible()) { - res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE; + return res; + } + + private int addWindowInner(@NonNull WindowState win, @NonNull DisplayPolicy displayPolicy, + @NonNull ActivityRecord activity, @NonNull DisplayContent displayContent, + @NonNull InsetsState outInsetsState, @NonNull Rect outAttachedFrame, + @NonNull InsetsSourceControl.Array outActiveControls, @NonNull IWindow client, + @NonNull float[] outSizeCompatScale, @NonNull LayoutParams attrs) { + int res = 0; + final int type = attrs.type; + boolean imMayMove = true; + + win.mToken.addWindow(win); + displayPolicy.addWindowLw(win, attrs); + 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 + // IME window is always touchable. + // Ignore non-touchable windows e.g. Stylus InkWindow.java. + && (win.getAttrs().flags & FLAG_NOT_TOUCHABLE) == 0) { + displayContent.setInputMethodWindowLocked(win); + imMayMove = false; + } else if (type == TYPE_INPUT_METHOD_DIALOG) { + displayContent.computeImeTarget(true /* updateImeTarget */); + imMayMove = false; + } else { + if (type == TYPE_WALLPAPER) { + displayContent.mWallpaperController.clearLastWallpaperTimeoutTime(); + displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; + } else if (win.hasWallpaper()) { + displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; + } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) { + // If there is currently a wallpaper being shown, and + // the base layer of the new window is below the current + // layer of the target window, then adjust the wallpaper. + // This is to avoid a new window being placed between the + // wallpaper and its target. + displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } + } - displayContent.getInputMonitor().setUpdateInputWindowsNeededLw(); + final WindowStateAnimator winAnimator = win.mWinAnimator; + winAnimator.mEnterAnimationPending = true; + winAnimator.mEnteringAnimation = true; - boolean focusChanged = false; - if (win.canReceiveKeys()) { - focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS, - false /*updateInputWindows*/); - if (focusChanged) { - imMayMove = false; - } - } + if (displayPolicy.areSystemBarsForcedConsumedLw()) { + res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS; + } + if (displayContent.isInTouchMode()) { + res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE; + } + if (win.mActivityRecord == null || win.mActivityRecord.isClientVisible()) { + res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE; + } - if (imMayMove) { - displayContent.computeImeTarget(true /* updateImeTarget */); - if (win.isImeOverlayLayeringTarget()) { - dispatchImeTargetOverlayVisibilityChanged(client.asBinder(), win.mAttrs.type, - win.isVisibleRequestedOrAdding(), false /* removed */, - displayContent.getDisplayId()); - } - } + displayContent.getInputMonitor().setUpdateInputWindowsNeededLw(); - // Don't do layout here, the window must call - // relayout to be displayed, so we'll do it there. - if (win.mActivityRecord != null && win.mActivityRecord.isEmbedded()) { - // Assign child layers from the parent Task if the Activity is embedded. - win.getTask().assignChildLayers(); - } else { - win.getParent().assignChildLayers(); + boolean focusChanged = false; + if (win.canReceiveKeys()) { + focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS, + false /*updateInputWindows*/); + if (focusChanged) { + imMayMove = false; } + } - if (focusChanged) { - displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus, - false /*updateInputWindows*/); + if (imMayMove) { + displayContent.computeImeTarget(true /* updateImeTarget */); + if (win.isImeOverlayLayeringTarget()) { + dispatchImeTargetOverlayVisibilityChanged(client.asBinder(), win.mAttrs.type, + win.isVisibleRequestedOrAdding(), false /* removed */, + displayContent.getDisplayId()); } - displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/); + } - ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s" - + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5)); + // Don't do layout here, the window must call + // relayout to be displayed, so we'll do it there. + if (win.mActivityRecord != null && win.mActivityRecord.isEmbedded()) { + // Assign child layers from the parent Task if the Activity is embedded. + win.getTask().assignChildLayers(); + } else { + win.getParent().assignChildLayers(); + } - boolean needToSendNewConfiguration = - win.isVisibleRequestedOrAdding() && displayContent.updateOrientation(); - if (win.providesDisplayDecorInsets()) { - needToSendNewConfiguration |= displayPolicy.updateDecorInsetsInfo(); - } - if (needToSendNewConfiguration) { - displayContent.sendNewConfiguration(); - } + if (focusChanged) { + displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus, + false /*updateInputWindows*/); + } + displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/); - // This window doesn't have a frame yet. Don't let this window cause the insets change. - displayContent.getInsetsStateController().updateAboveInsetsState( - false /* notifyInsetsChanged */); + ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s" + + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5)); - win.fillInsetsState(outInsetsState, true /* copySources */); - getInsetsSourceControls(win, outActiveControls); + boolean needToSendNewConfiguration = + win.isVisibleRequestedOrAdding() && displayContent.updateOrientation(); + if (win.providesDisplayDecorInsets()) { + needToSendNewConfiguration |= displayPolicy.updateDecorInsetsInfo(); + } + if (needToSendNewConfiguration) { + displayContent.sendNewConfiguration(); + } - if (win.mLayoutAttached) { - outAttachedFrame.set(win.getParentWindow().getFrame()); - if (win.mInvGlobalScale != 1f) { - outAttachedFrame.scale(win.mInvGlobalScale); - } - } else { - // Make this invalid which indicates a null attached frame. - outAttachedFrame.set(0, 0, -1, -1); - } - outSizeCompatScale[0] = win.getCompatScaleForClient(); + // This window doesn't have a frame yet. Don't let this window cause the insets change. + displayContent.getInsetsStateController().updateAboveInsetsState( + false /* notifyInsetsChanged */); + + win.fillInsetsState(outInsetsState, true /* copySources */); + getInsetsSourceControls(win, outActiveControls); - if (res >= ADD_OKAY && win.isPresentation()) { - mPresentationController.onPresentationAdded(win); + if (win.mLayoutAttached) { + outAttachedFrame.set(win.getParentWindow().getFrame()); + if (win.mInvGlobalScale != 1f) { + outAttachedFrame.scale(win.mInvGlobalScale); } + } else { + // Make this invalid which indicates a null attached frame. + outAttachedFrame.set(0, 0, -1, -1); } + outSizeCompatScale[0] = win.getCompatScaleForClient(); - Binder.restoreCallingIdentity(origId); + if (res >= ADD_OKAY && win.isPresentation()) { + mPresentationController.onPresentationAdded(win); + } return res; } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 4c53ba53a506..4b4736ec6c59 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1060,7 +1060,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub effects |= applyChanges(taskFragment, c); if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) { - taskFragment.initializeChangeTransition(mTmpBounds0); + mTransitionController.collectVisibleChange(taskFragment); } taskFragment.continueOrganizedTaskFragmentSurfaceUpdate(); return effects; diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 30dde543b9d4..b2d28a369edc 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -37,6 +37,7 @@ 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.ActivityRecord.State.STOPPED; 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; @@ -344,6 +345,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio */ private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER; + /** + * The most recent timestamp of when one of this process's stopped activities in a + * perceptible task became stopped. Written by window manager and read by activity manager. + */ + private volatile long mPerceptibleTaskStoppedTimeMillis = Long.MIN_VALUE; + public WindowProcessController(@NonNull ActivityTaskManagerService atm, @NonNull ApplicationInfo info, String name, int uid, int userId, Object owner, @NonNull WindowProcessListener listener) { @@ -1228,6 +1235,17 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return mActivityStateFlags; } + /** + * Returns the most recent timestamp when one of this process's stopped activities in a + * perceptible task became stopped. It should only be called if {@link #hasActivities} + * returns {@code true} and {@link #getActivityStateFlags} does not have any of + * the ACTIVITY_STATE_FLAG_IS_(VISIBLE|PAUSING_OR_PAUSED|STOPPING) bit set. + */ + @HotPath(caller = HotPath.OOM_ADJUSTMENT) + public long getPerceptibleTaskStoppedTimeMillis() { + return mPerceptibleTaskStoppedTimeMillis; + } + void computeProcessActivityState() { // 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 @@ -1239,6 +1257,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio int minTaskLayer = Integer.MAX_VALUE; int stateFlags = 0; int nonOccludedRatio = 0; + long perceptibleTaskStoppedTimeMillis = Long.MIN_VALUE; final boolean wasResumed = hasResumedActivity(); final boolean wasAnyVisible = (mActivityStateFlags & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0; @@ -1287,6 +1306,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio bestInvisibleState = STOPPING; // Not "finishing" if any of activity isn't finishing. allStoppingFinishing &= r.finishing; + } else if (bestInvisibleState == DESTROYED && r.isState(STOPPED)) { + if (task.mIsPerceptible) { + perceptibleTaskStoppedTimeMillis = + Long.max(r.mStoppedTime, perceptibleTaskStoppedTimeMillis); + } } } } @@ -1324,6 +1348,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } } mActivityStateFlags = stateFlags; + mPerceptibleTaskStoppedTimeMillis = perceptibleTaskStoppedTimeMillis; final boolean anyVisible = (stateFlags & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 589724182980..92ad2cec364b 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -95,6 +95,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; +import static android.view.WindowManager.TRANSIT_CLOSE; 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; @@ -182,6 +183,7 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY; import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES; +import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays; import static com.android.window.flags.Flags.surfaceTrustedOverlay; import android.annotation.CallSuper; @@ -2297,11 +2299,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP dc.updateImeInputAndControlTarget(null); } - final int type = mAttrs.type; - - if (isPresentation()) { - mWmService.mPresentationController.onPresentationRemoved(this); - } // Check if window provides non decor insets before clearing its provided insets. final boolean windowProvidesDisplayDecorInsets = providesDisplayDecorInsets(); @@ -2442,11 +2439,33 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } - removeImmediately(); - mWmService.updateFocusedWindowLocked(isFocused() - ? UPDATE_FOCUS_REMOVING_FOCUS - : UPDATE_FOCUS_NORMAL, - true /*updateInputWindows*/); + // Only a presentation window needs a transition because its visibility affets the + // lifecycle of apps below (b/390481865). + if (enablePresentationForConnectedDisplays() && isPresentation()) { + Transition transition = null; + if (!mTransitionController.isCollecting()) { + transition = mTransitionController.createAndStartCollecting(TRANSIT_CLOSE); + } + mTransitionController.collect(mToken); + mAnimatingExit = true; + mRemoveOnExit = true; + mToken.setVisibleRequested(false); + mWmService.mPresentationController.onPresentationRemoved(this); + // A presentation hides all activities behind on the same display. + mDisplayContent.ensureActivitiesVisible(/*starting=*/ null, + /*notifyClients=*/ true); + mTransitionController.getCollectingTransition().setReady(mToken, true); + if (transition != null) { + mTransitionController.requestStartTransition(transition, null, + null /* remoteTransition */, null /* displayChange */); + } + } else { + removeImmediately(); + mWmService.updateFocusedWindowLocked(isFocused() + ? UPDATE_FOCUS_REMOVING_FOCUS + : UPDATE_FOCUS_NORMAL, + true /*updateInputWindows*/); + } } finally { Binder.restoreCallingIdentity(origId); } @@ -3272,13 +3291,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mDestroying = false; destroyedSomething = true; } - - // Since mDestroying will affect ActivityRecord#allDrawn, we need to perform another - // traversal in case we are waiting on this window to start the transition. - if (getDisplayContent().mAppTransition.isTransitionSet() - && getDisplayContent().mOpeningApps.contains(mActivityRecord)) { - mWmService.mWindowPlacerLocked.requestTraversal(); - } } return destroyedSomething; diff --git a/services/core/java/com/android/server/wm/utils/WindowStyleCache.java b/services/core/java/com/android/server/wm/utils/WindowStyleCache.java new file mode 100644 index 000000000000..a253c2c75aa2 --- /dev/null +++ b/services/core/java/com/android/server/wm/utils/WindowStyleCache.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2025 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.utils; + +import android.content.res.TypedArray; +import android.util.ArrayMap; +import android.util.SparseArray; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.policy.AttributeCache; + +import java.util.function.Function; + +/** + * A wrapper of AttributeCache to preserve more dedicated style caches. + * @param <T> The type of style cache. + */ +public class WindowStyleCache<T> { + @GuardedBy("itself") + private final ArrayMap<String, SparseArray<T>> mCache = new ArrayMap<>(); + private final Function<TypedArray, T> mEntryFactory; + + public WindowStyleCache(Function<TypedArray, T> entryFactory) { + mEntryFactory = entryFactory; + } + + /** Returns the cached entry. */ + public T get(String packageName, int theme, int userId) { + SparseArray<T> themeMap; + synchronized (mCache) { + themeMap = mCache.get(packageName); + if (themeMap != null) { + T style = themeMap.get(theme); + if (style != null) { + return style; + } + } + } + + final AttributeCache attributeCache = AttributeCache.instance(); + if (attributeCache == null) { + return null; + } + final AttributeCache.Entry ent = attributeCache.get(packageName, theme, + R.styleable.Window, userId); + if (ent == null) { + return null; + } + + final T style = mEntryFactory.apply(ent.array); + synchronized (mCache) { + if (themeMap == null) { + mCache.put(packageName, themeMap = new SparseArray<>()); + } + themeMap.put(theme, style); + } + return style; + } + + /** Called when the package is updated or removed. */ + public void invalidatePackage(String packageName) { + synchronized (mCache) { + mCache.remove(packageName); + } + } +} diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index f07e6722d836..a8c49e11e4e9 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -47,6 +47,7 @@ #include <dispatcher/Entry.h> #include <include/gestures.h> #include <input/Input.h> +#include <input/InputFlags.h> #include <input/PointerController.h> #include <input/PrintTools.h> #include <input/SpriteController.h> @@ -124,6 +125,7 @@ static struct { jmethodID notifyStylusGestureStarted; jmethodID notifyVibratorState; jmethodID filterInputEvent; + jmethodID filterPointerMotion; jmethodID interceptKeyBeforeQueueing; jmethodID interceptMotionBeforeQueueingNonInteractive; jmethodID interceptKeyBeforeDispatching; @@ -451,6 +453,8 @@ public: void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId, const vec2& position) override; void notifyMouseCursorFadedOnTyping() override; + std::optional<vec2> filterPointerMotionForAccessibility( + const vec2& current, const vec2& delta, const ui::LogicalDisplayId& displayId) override; /* --- InputFilterPolicyInterface implementation --- */ void notifyStickyModifierStateChanged(uint32_t modifierState, @@ -663,7 +667,7 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO } void NativeInputManager::setDisplayTopology(JNIEnv* env, jobject topologyGraph) { - if (!com::android::input::flags::connected_displays_cursor()) { + if (!InputFlags::connectedDisplaysCursorEnabled()) { return; } @@ -938,6 +942,27 @@ void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState checkAndClearExceptionFromCallback(env, "notifyStickyModifierStateChanged"); } +std::optional<vec2> NativeInputManager::filterPointerMotionForAccessibility( + const vec2& current, const vec2& delta, const ui::LogicalDisplayId& displayId) { + JNIEnv* env = jniEnv(); + ScopedFloatArrayRO filtered(env, + jfloatArray( + env->CallObjectMethod(mServiceObj, + gServiceClassInfo.filterPointerMotion, + delta.x, delta.y, current.x, + current.y, displayId.val()))); + if (checkAndClearExceptionFromCallback(env, "filterPointerMotionForAccessibilityLocked")) { + ALOGE("Disabling accessibility pointer motion filter due to an error. " + "The filter state in Java and PointerChoreographer would no longer be in sync."); + return std::nullopt; + } + LOG_ALWAYS_FATAL_IF(filtered.size() != 2, + "Accessibility pointer motion filter is misbehaving. Returned array size " + "%zu should be 2.", + filtered.size()); + return vec2{filtered[0], filtered[1]}; +} + sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(ui::LogicalDisplayId displayId) { JNIEnv* env = jniEnv(); jlong nativeSurfaceControlPtr = @@ -3271,6 +3296,12 @@ static jboolean nativeSetKernelWakeEnabled(JNIEnv* env, jobject nativeImplObj, j return im->getInputManager()->getReader().setKernelWakeEnabled(deviceId, enabled); } +static void nativeSetAccessibilityPointerMotionFilterEnabled(JNIEnv* env, jobject nativeImplObj, + jboolean enabled) { + NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + im->getInputManager()->getChoreographer().setAccessibilityPointerMotionFilterEnabled(enabled); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gInputManagerMethods[] = { @@ -3398,6 +3429,8 @@ static const JNINativeMethod gInputManagerMethods[] = { {"setInputMethodConnectionIsActive", "(Z)V", (void*)nativeSetInputMethodConnectionIsActive}, {"getLastUsedInputDeviceId", "()I", (void*)nativeGetLastUsedInputDeviceId}, {"setKernelWakeEnabled", "(IZ)Z", (void*)nativeSetKernelWakeEnabled}, + {"setAccessibilityPointerMotionFilterEnabled", "(Z)V", + (void*)nativeSetAccessibilityPointerMotionFilterEnabled}, }; #define FIND_CLASS(var, className) \ @@ -3482,6 +3515,8 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.filterInputEvent, clazz, "filterInputEvent", "(Landroid/view/InputEvent;I)Z"); + GET_METHOD_ID(gServiceClassInfo.filterPointerMotion, clazz, "filterPointerMotion", "(FFFFI)[F"); + GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeQueueing, clazz, "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;I)I"); diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java index 40554ac51b24..7edf646a48f6 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java @@ -21,11 +21,16 @@ import static com.android.server.credentials.CredentialManagerService.getPrimary import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.credentials.CredentialManager; import android.credentials.CredentialProviderInfo; +import android.credentials.flags.Flags; import android.service.credentials.CredentialProviderInfoFactory; +import android.service.credentials.CredentialProviderService; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -78,14 +83,16 @@ public final class CredentialManagerServiceImpl extends mInfo = providerInfo; } - @Override // from PerUserSystemService when a new setting based service is to be created + @Override // from PerUserSystemService when a new service is to be created @GuardedBy("mLock") protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) - throws PackageManager.NameNotFoundException { + throws PackageManager.NameNotFoundException, SecurityException, NullPointerException { + boolean isSystemProvider = false; if (mInfo != null) { Slog.i(TAG, "newServiceInfoLocked, mInfo not null : " + mInfo.getServiceInfo().getComponentName().flattenToString() + " , " + serviceComponent.flattenToString()); + isSystemProvider = mInfo.isSystemProvider(); } else { Slog.i(TAG, "newServiceInfoLocked, mInfo null, " + serviceComponent.flattenToString()); @@ -94,7 +101,7 @@ public final class CredentialManagerServiceImpl extends getPrimaryProvidersForUserId(mMaster.getContext(), mUserId); mInfo = CredentialProviderInfoFactory.create( getContext(), serviceComponent, - mUserId, /*isSystemProvider=*/false, + mUserId, isSystemProvider, primaryProviders.contains(serviceComponent)); return mInfo.getServiceInfo(); } @@ -148,15 +155,63 @@ public final class CredentialManagerServiceImpl extends * @param packageName package of the app being updated. */ @GuardedBy("mLock") + @SuppressWarnings("GuardedBy") // ErrorProne requires this.mMaster.mLock which is the case + // because this method is called by this.mMaster anyway protected void handlePackageUpdateLocked(@NonNull String packageName) { if (mInfo != null && mInfo.getServiceInfo() != null && mInfo.getServiceInfo().getComponentName() .getPackageName().equals(packageName)) { - try { - newServiceInfoLocked(mInfo.getServiceInfo().getComponentName()); - } catch (PackageManager.NameNotFoundException e) { - Slog.e(TAG, "Issue while updating serviceInfo: " + e.getMessage()); + if (Flags.packageUpdateFixEnabled()) { + try { + updateCredentialProviderInfo(mInfo.getServiceInfo().getComponentName(), + mInfo.isSystemProvider()); + } catch (SecurityException | PackageManager.NameNotFoundException + | NullPointerException e) { + Slog.w(TAG, "Unable to update provider, must be removed: " + e.getMessage()); + mMaster.handleServiceRemovedMultiModeLocked(mInfo.getComponentName(), mUserId); + } + } else { + try { + newServiceInfoLocked(mInfo.getServiceInfo().getComponentName()); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Issue while updating serviceInfo: " + e.getMessage()); + } + } + } + } + + @GuardedBy("mLock") + private void updateCredentialProviderInfo(ComponentName componentName, boolean isSystemProvider) + throws SecurityException, PackageManager.NameNotFoundException { + Slog.d(TAG, "Updating credential provider: " + componentName.flattenToString()); + if (!isValidCredentialProviderInfo(componentName, mUserId, isSystemProvider)) { + throw new SecurityException("Service has not been set up correctly"); + } + newServiceInfoLocked(componentName); + } + + private boolean isValidCredentialProviderInfo(ComponentName componentName, int userId, + boolean isSystemProvider) { + Context context = getContext(); + if (context == null) { + return false; + } + String serviceInterface = CredentialProviderService.SERVICE_INTERFACE; + if (isSystemProvider) { + serviceInterface = CredentialProviderService.SYSTEM_SERVICE_INTERFACE; + } + final List<ResolveInfo> resolveInfos = + context.getPackageManager() + .queryIntentServicesAsUser( + new Intent(serviceInterface), + PackageManager.ResolveInfoFlags.of(PackageManager.GET_META_DATA), + userId); + for (ResolveInfo resolveInfo : resolveInfos) { + final ServiceInfo serviceInfo = resolveInfo.serviceInfo; + if (serviceInfo.getComponentName().equals(componentName)) { + return true; } } + return false; } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index bbbfe0b2f0a2..5b7e7f17c454 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -65,6 +65,7 @@ import android.util.Log; import android.util.SparseArray; import android.util.Xml; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -120,11 +121,13 @@ final class DevicePolicyEngine { /** * Map of <userId, Map<policyKey, policyState>> */ + @GuardedBy("mLock") private final SparseArray<Map<PolicyKey, PolicyState<?>>> mLocalPolicies; /** * Map of <policyKey, policyState> */ + @GuardedBy("mLock") private final Map<PolicyKey, PolicyState<?>> mGlobalPolicies; /** @@ -152,6 +155,7 @@ final class DevicePolicyEngine { mAdminPolicySize = new SparseArray<>(); } + @GuardedBy("mLock") private void forceEnforcementRefreshIfUserRestrictionLocked( @NonNull PolicyDefinition<?> policyDefinition) { try { @@ -185,6 +189,7 @@ final class DevicePolicyEngine { return false; } + @GuardedBy("mLock") private void forceEnforcementRefreshLocked(PolicyDefinition<Boolean> policyDefinition) { Binder.withCleanCallingIdentity(() -> { // Sync global state @@ -296,6 +301,7 @@ final class DevicePolicyEngine { * * <p>Passing a {@code null} value means the policy set by this admin should be removed. */ + @GuardedBy("mLock") private <V> void setNonCoexistableLocalPolicyLocked( PolicyDefinition<V> policyDefinition, PolicyState<V> localPolicyState, @@ -440,6 +446,7 @@ final class DevicePolicyEngine { /** * Enforces the new policy and notifies relevant admins. */ + @GuardedBy("mLock") private <V> void onLocalPolicyChangedLocked( @NonNull PolicyDefinition<V> policyDefinition, @NonNull EnforcingAdmin enforcingAdmin, @@ -600,6 +607,7 @@ final class DevicePolicyEngine { /** * Enforces the new policy globally and notifies relevant admins. */ + @GuardedBy("mLock") private <V> void onGlobalPolicyChangedLocked( @NonNull PolicyDefinition<V> policyDefinition, @NonNull EnforcingAdmin enforcingAdmin) { @@ -627,6 +635,7 @@ final class DevicePolicyEngine { * * <p>Returns {@code true} if the policy is enforced successfully on all users. */ + @GuardedBy("mLock") private <V> boolean applyGlobalPolicyOnUsersWithLocalPoliciesLocked( @NonNull PolicyDefinition<V> policyDefinition, @NonNull EnforcingAdmin enforcingAdmin, @@ -930,6 +939,7 @@ final class DevicePolicyEngine { removePoliciesForAdmin(oldAdmin); } + @GuardedBy("mLock") private Set<UserRestrictionPolicyKey> getUserRestrictionPolicyKeysForAdminLocked( Map<PolicyKey, PolicyState<?>> policies, EnforcingAdmin admin) { @@ -949,6 +959,7 @@ final class DevicePolicyEngine { return keys; } + @GuardedBy("mLock") private <V> boolean hasLocalPolicyLocked(PolicyDefinition<V> policyDefinition, int userId) { if (policyDefinition.isGlobalOnlyPolicy()) { return false; @@ -963,6 +974,7 @@ final class DevicePolicyEngine { .getPoliciesSetByAdmins().isEmpty(); } + @GuardedBy("mLock") private <V> boolean hasGlobalPolicyLocked(PolicyDefinition<V> policyDefinition) { if (policyDefinition.isLocalOnlyPolicy()) { return false; @@ -974,6 +986,7 @@ final class DevicePolicyEngine { .isEmpty(); } + @GuardedBy("mLock") @NonNull private <V> PolicyState<V> getLocalPolicyStateLocked( PolicyDefinition<V> policyDefinition, int userId) { @@ -993,6 +1006,7 @@ final class DevicePolicyEngine { return getPolicyStateLocked(mLocalPolicies.get(userId), policyDefinition); } + @GuardedBy("mLock") private <V> void removeLocalPolicyStateLocked( PolicyDefinition<V> policyDefinition, int userId) { if (!mLocalPolicies.contains(userId)) { @@ -1001,6 +1015,7 @@ final class DevicePolicyEngine { mLocalPolicies.get(userId).remove(policyDefinition.getPolicyKey()); } + @GuardedBy("mLock") @NonNull private <V> PolicyState<V> getGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) { if (policyDefinition.isLocalOnlyPolicy()) { @@ -1015,10 +1030,12 @@ final class DevicePolicyEngine { return getPolicyStateLocked(mGlobalPolicies, policyDefinition); } + @GuardedBy("mLock") private <V> void removeGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) { mGlobalPolicies.remove(policyDefinition.getPolicyKey()); } + @GuardedBy("mLock") private static <V> PolicyState<V> getPolicyStateLocked( Map<PolicyKey, PolicyState<?>> policies, PolicyDefinition<V> policyDefinition) { try { @@ -1089,6 +1106,7 @@ final class DevicePolicyEngine { } // TODO(b/261430877): Finalise the decision on which admins to send the updates to. + @GuardedBy("mLock") private <V> void sendPolicyChangedToAdminsLocked( PolicyState<V> policyState, EnforcingAdmin callingAdmin, @@ -1378,6 +1396,7 @@ final class DevicePolicyEngine { }); } + @GuardedBy("mLock") private <V> void enforcePolicyOnUserLocked(int userId, PolicyState<V> policyState) { if (!policyState.getPolicyDefinition().isInheritable()) { return; @@ -1509,6 +1528,7 @@ final class DevicePolicyEngine { * Called after an admin policy has been added to start binding to the admin if a connection * was not already established. */ + @GuardedBy("mLock") private void updateDeviceAdminServiceOnPolicyAddLocked(@NonNull EnforcingAdmin enforcingAdmin) { int userId = enforcingAdmin.getUserId(); @@ -1537,6 +1557,7 @@ final class DevicePolicyEngine { * Called after an admin policy has been removed to stop binding to the admin if they no longer * have any policies set. */ + @GuardedBy("mLock") private void updateDeviceAdminServiceOnPolicyRemoveLocked( @NonNull EnforcingAdmin enforcingAdmin) { if (doesAdminHavePoliciesLocked(enforcingAdmin)) { @@ -1562,6 +1583,7 @@ final class DevicePolicyEngine { /* actionForLog= */ "policy-removed"); } + @GuardedBy("mLock") private boolean doesAdminHavePoliciesLocked(@NonNull EnforcingAdmin enforcingAdmin) { for (PolicyKey policy : mGlobalPolicies.keySet()) { PolicyState<?> policyState = mGlobalPolicies.get(policy); @@ -1785,6 +1807,7 @@ final class DevicePolicyEngine { } } + @GuardedBy("mLock") <V> void reapplyAllPoliciesOnBootLocked() { for (PolicyKey policy : mGlobalPolicies.keySet()) { PolicyState<?> policyState = mGlobalPolicies.get(policy); @@ -1919,6 +1942,7 @@ final class DevicePolicyEngine { } } + @GuardedBy("mLock") void writeToFileLocked() { Log.d(TAG, "Writing to " + mFile); @@ -1931,7 +1955,7 @@ final class DevicePolicyEngine { out.startDocument(null, true); // Actual content - writeInner(out); + writeInnerLocked(out); out.endDocument(); out.flush(); @@ -1948,16 +1972,19 @@ final class DevicePolicyEngine { } } + @GuardedBy("mLock") // TODO(b/256846294): Add versioning to read/write - void writeInner(TypedXmlSerializer serializer) throws IOException { - writeLocalPoliciesInner(serializer); - writeGlobalPoliciesInner(serializer); - writeEnforcingAdminsInner(serializer); - writeEnforcingAdminSizeInner(serializer); - writeMaxPolicySizeInner(serializer); + void writeInnerLocked(TypedXmlSerializer serializer) throws IOException { + writeLocalPoliciesInnerLocked(serializer); + writeGlobalPoliciesInnerLocked(serializer); + writeEnforcingAdminsInnerLocked(serializer); + writeEnforcingAdminSizeInnerLocked(serializer); + writeMaxPolicySizeInnerLocked(serializer); } - private void writeLocalPoliciesInner(TypedXmlSerializer serializer) throws IOException { + @GuardedBy("mLock") + private void writeLocalPoliciesInnerLocked(TypedXmlSerializer serializer) + throws IOException { if (mLocalPolicies != null) { for (int i = 0; i < mLocalPolicies.size(); i++) { int userId = mLocalPolicies.keyAt(i); @@ -1981,7 +2008,9 @@ final class DevicePolicyEngine { } } - private void writeGlobalPoliciesInner(TypedXmlSerializer serializer) throws IOException { + @GuardedBy("mLock") + private void writeGlobalPoliciesInnerLocked(TypedXmlSerializer serializer) + throws IOException { if (mGlobalPolicies != null) { for (Map.Entry<PolicyKey, PolicyState<?>> policy : mGlobalPolicies.entrySet()) { serializer.startTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY); @@ -1999,7 +2028,9 @@ final class DevicePolicyEngine { } } - private void writeEnforcingAdminsInner(TypedXmlSerializer serializer) throws IOException { + @GuardedBy("mLock") + private void writeEnforcingAdminsInnerLocked(TypedXmlSerializer serializer) + throws IOException { if (mEnforcingAdmins != null) { for (int i = 0; i < mEnforcingAdmins.size(); i++) { int userId = mEnforcingAdmins.keyAt(i); @@ -2012,7 +2043,8 @@ final class DevicePolicyEngine { } } - private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer) + @GuardedBy("mLock") + private void writeEnforcingAdminSizeInnerLocked(TypedXmlSerializer serializer) throws IOException { if (mAdminPolicySize != null) { for (int i = 0; i < mAdminPolicySize.size(); i++) { @@ -2034,7 +2066,8 @@ final class DevicePolicyEngine { } } - private void writeMaxPolicySizeInner(TypedXmlSerializer serializer) + @GuardedBy("mLock") + private void writeMaxPolicySizeInnerLocked(TypedXmlSerializer serializer) throws IOException { serializer.startTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT); serializer.attributeInt( @@ -2042,6 +2075,7 @@ final class DevicePolicyEngine { serializer.endTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT); } + @GuardedBy("mLock") void readFromFileLocked() { if (!mFile.exists()) { Log.d(TAG, "" + mFile + " doesn't exist"); @@ -2055,7 +2089,7 @@ final class DevicePolicyEngine { input = f.openRead(); TypedXmlPullParser parser = Xml.resolvePullParser(input); - readInner(parser); + readInnerLocked(parser); } catch (XmlPullParserException | IOException | ClassNotFoundException e) { Slogf.wtf(TAG, "Error parsing resources file", e); @@ -2064,7 +2098,8 @@ final class DevicePolicyEngine { } } - private void readInner(TypedXmlPullParser parser) + @GuardedBy("mLock") + private void readInnerLocked(TypedXmlPullParser parser) throws IOException, XmlPullParserException, ClassNotFoundException { int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 191c21e661d0..aee32a0473a3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -423,6 +423,7 @@ import android.database.ContentObserver; import android.database.Cursor; import android.graphics.Bitmap; import android.hardware.usb.UsbManager; +import android.health.connect.HealthConnectManager; import android.location.Location; import android.location.LocationManager; import android.media.AudioManager; @@ -2149,6 +2150,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); mBackgroundHandler = BackgroundThread.getHandler(); + // Add the health permission to the list of restricted permissions. + if (android.permission.flags.Flags.replaceBodySensorPermissionEnabled()) { + Set<String> healthPermissions = HealthConnectManager.getHealthPermissions(mContext); + for (String permission : healthPermissions) { + SENSOR_PERMISSIONS.add(permission); + } + } + // Needed when mHasFeature == false, because it controls the certificate warning text. mCertificateMonitor = new CertificateMonitor(this, mInjector, mBackgroundHandler); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 2bbd69c65eb8..e158310455ac 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1279,12 +1279,6 @@ public final class SystemServer implements Dumpable { if (!Flags.refactorCrashrecovery()) { // Initialize RescueParty. CrashRecoveryAdaptor.rescuePartyRegisterHealthObserver(mSystemContext); - if (!Flags.recoverabilityDetection()) { - // Now that we have the bare essentials of the OS up and running, take - // note that we just booted, which might send out a rescue party if - // we're stuck in a runtime restart loop. - CrashRecoveryAdaptor.packageWatchdogNoteBoot(mSystemContext); - } } @@ -1558,14 +1552,6 @@ public final class SystemServer implements Dumpable { boolean enableVrService = context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE); - if (!Flags.recoverabilityDetection()) { - // For debugging RescueParty - if (Build.IS_DEBUGGABLE - && SystemProperties.getBoolean("debug.crash_system", false)) { - throw new RuntimeException(); - } - } - try { final String SECONDARY_ZYGOTE_PRELOAD = "SecondaryZygotePreload"; // We start the preload ~1s before the webview factory preparation, to @@ -3091,13 +3077,11 @@ public final class SystemServer implements Dumpable { CrashRecoveryAdaptor.initializeCrashrecoveryModuleService(mSystemServiceManager); t.traceEnd(); } else { - if (Flags.recoverabilityDetection()) { - // Now that we have the essential services needed for mitigations, register the boot - // with package watchdog. - // Note that we just booted, which might send out a rescue party if we're stuck in a - // runtime restart loop. - CrashRecoveryAdaptor.packageWatchdogNoteBoot(mSystemContext); - } + // Now that we have the essential services needed for mitigations, register the boot + // with package watchdog. + // Note that we just booted, which might send out a rescue party if we're stuck in a + // runtime restart loop. + CrashRecoveryAdaptor.packageWatchdogNoteBoot(mSystemContext); } t.traceBegin("MakeDisplayManagerServiceReady"); @@ -3511,12 +3495,10 @@ public final class SystemServer implements Dumpable { * are updated outside of OTA; and to avoid breaking dependencies from system into apexes. */ private void startApexServices(@NonNull TimingsTraceAndSlog t) { - if (Flags.recoverabilityDetection()) { - // For debugging RescueParty - if (Build.IS_DEBUGGABLE - && SystemProperties.getBoolean("debug.crash_system", false)) { - throw new RuntimeException(); - } + // For debugging RescueParty + if (Build.IS_DEBUGGABLE + && SystemProperties.getBoolean("debug.crash_system", false)) { + throw new RuntimeException(); } t.traceBegin("startApexServices"); diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java index a96c477c78d2..f731b50d81b4 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionService.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java @@ -17,6 +17,8 @@ package com.android.server.supervision; import static android.Manifest.permission.INTERACT_ACROSS_USERS; +import static android.Manifest.permission.MANAGE_USERS; +import static android.Manifest.permission.QUERY_USERS; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static com.android.internal.util.Preconditions.checkCallAuthorization; @@ -79,6 +81,25 @@ public class SupervisionService extends ISupervisionManager.Stub { } /** + * Creates an {@link Intent} that can be used with {@link Context#startActivity(Intent)} to + * launch the activity to verify supervision credentials. + * + * <p>A valid {@link Intent} is always returned if supervision is enabled at the time this + * method is called, the launched activity still need to perform validity checks as the + * supervision state can change when it's launched. A null intent is returned if supervision is + * disabled at the time of this method call. + * + * <p>A result code of {@link android.app.Activity#RESULT_OK} indicates successful verification + * of the supervision credentials. + */ + @Override + @Nullable + public Intent createConfirmSupervisionCredentialsIntent() { + // TODO(b/392961554): Implement createAuthenticationIntent API + throw new UnsupportedOperationException(); + } + + /** * Returns whether supervision is enabled for the given user. * * <p>Supervision is automatically enabled when the supervision app becomes the profile owner or @@ -86,6 +107,7 @@ public class SupervisionService extends ISupervisionManager.Stub { */ @Override public boolean isSupervisionEnabledForUser(@UserIdInt int userId) { + enforceAnyPermission(QUERY_USERS, MANAGE_USERS); if (UserHandle.getUserId(Binder.getCallingUid()) != userId) { enforcePermission(INTERACT_ACROSS_USERS); } @@ -96,6 +118,7 @@ public class SupervisionService extends ISupervisionManager.Stub { @Override public void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) { + // TODO(b/395630828): Ensure that this method can only be called by the system. if (UserHandle.getUserId(Binder.getCallingUid()) != userId) { enforcePermission(INTERACT_ACROSS_USERS); } @@ -181,8 +204,8 @@ public class SupervisionService extends ISupervisionManager.Stub { * Ensures that supervision is enabled when the supervision app is the profile owner. * * <p>The state syncing with the DevicePolicyManager can only enable supervision and never - * disable. Supervision can only be disabled explicitly via calls to the - * {@link #setSupervisionEnabledForUser} method. + * disable. Supervision can only be disabled explicitly via calls to the {@link + * #setSupervisionEnabledForUser} method. */ private void syncStateWithDevicePolicyManager(@UserIdInt int userId) { final DevicePolicyManagerInternal dpmInternal = mInjector.getDpmInternal(); @@ -221,6 +244,17 @@ public class SupervisionService extends ISupervisionManager.Stub { mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED); } + /** Enforces that the caller has at least one of the given permission. */ + private void enforceAnyPermission(String... permissions) { + boolean authorized = false; + for (String permission : permissions) { + if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { + authorized = true; + } + } + checkCallAuthorization(authorized); + } + /** Provides local services in a lazy manner. */ static class Injector { private final Context mContext; @@ -280,7 +314,7 @@ public class SupervisionService extends ISupervisionManager.Stub { } @VisibleForTesting - @SuppressLint("MissingPermission") // not needed for a system service + @SuppressLint("MissingPermission") void registerProfileOwnerListener() { IntentFilter poIntentFilter = new IntentFilter(); poIntentFilter.addAction(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED); diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java index 5d64cb638702..ae5e85163e9a 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java @@ -34,12 +34,12 @@ import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; +import android.app.ActivityManager; import android.app.Instrumentation; import android.content.res.Configuration; import android.graphics.Insets; +import android.os.Build; import android.os.RemoteException; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.server.wm.WindowManagerStateHelper; @@ -86,25 +86,36 @@ public class InputMethodServiceTest { "android:id/input_method_nav_back"; private static final String INPUT_METHOD_NAV_IME_SWITCHER_ID = "android:id/input_method_nav_ime_switcher"; - private static final long TIMEOUT_IN_SECONDS = 3; - private static final String ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD = - "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 1"; - private static final String DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD = - "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 0"; + + /** Timeout until the uiObject should be found. */ + private static final long TIMEOUT_MS = 5000L * Build.HW_TIMEOUT_MULTIPLIER; + + /** Timeout until the event is expected. */ + private static final long EXPECT_TIMEOUT_MS = 3000L * Build.HW_TIMEOUT_MULTIPLIER; + + /** Timeout during which the event is not expected. */ + private static final long NOT_EXCEPT_TIMEOUT_MS = 2000L * Build.HW_TIMEOUT_MULTIPLIER; + + /** Command to set showing the IME when a hardware keyboard is connected. */ + private static final String SET_SHOW_IME_WITH_HARD_KEYBOARD_CMD = + "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD; + /** Command to get verbose ImeTracker logging state. */ + private static final String GET_VERBOSE_IME_TRACKER_LOGGING_CMD = + "getprop persist.debug.imetracker"; + /** Command to set verbose ImeTracker logging state. */ + private static final String SET_VERBOSE_IME_TRACKER_LOGGING_CMD = + "setprop persist.debug.imetracker"; /** The ids of the subtypes of SimpleIme. */ private static final int[] SUBTYPE_IDS = new int[]{1, 2}; - private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper(); + private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper(); private final GestureNavSwitchHelper mGestureNavSwitchHelper = new GestureNavSwitchHelper(); private final DeviceFlagsValueProvider mFlagsValueProvider = new DeviceFlagsValueProvider(); @Rule - public final CheckFlagsRule mCheckFlagsRule = new CheckFlagsRule(mFlagsValueProvider); - - @Rule public final TestName mName = new TestName(); private Instrumentation mInstrumentation; @@ -114,7 +125,8 @@ public class InputMethodServiceTest { private String mInputMethodId; private TestActivity mActivity; private InputMethodServiceWrapper mInputMethodService; - private boolean mShowImeWithHardKeyboardEnabled; + private boolean mOriginalVerboseImeTrackerLoggingEnabled; + private boolean mOriginalShowImeWithHardKeyboardEnabled; @Before public void setUp() throws Exception { @@ -123,9 +135,12 @@ public class InputMethodServiceTest { mImm = mInstrumentation.getContext().getSystemService(InputMethodManager.class); mTargetPackageName = mInstrumentation.getTargetContext().getPackageName(); mInputMethodId = getInputMethodId(); + mOriginalVerboseImeTrackerLoggingEnabled = getVerboseImeTrackerLogging(); + if (!mOriginalVerboseImeTrackerLoggingEnabled) { + setVerboseImeTrackerLogging(true); + } prepareIme(); prepareActivity(); - mInstrumentation.waitForIdleSync(); mUiDevice.freezeRotation(); mUiDevice.setOrientationNatural(); // Waits for input binding ready. @@ -148,17 +163,18 @@ public class InputMethodServiceTest { .that(mInputMethodService.getCurrentInputViewStarted()).isFalse(); }); // Save the original value of show_ime_with_hard_keyboard from Settings. - mShowImeWithHardKeyboardEnabled = + mOriginalShowImeWithHardKeyboardEnabled = mInputMethodService.getShouldShowImeWithHardKeyboardForTesting(); } @After public void tearDown() throws Exception { mUiDevice.unfreezeRotation(); + if (!mOriginalVerboseImeTrackerLoggingEnabled) { + setVerboseImeTrackerLogging(false); + } // Change back the original value of show_ime_with_hard_keyboard in Settings. - executeShellCommand(mShowImeWithHardKeyboardEnabled - ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD - : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD); + setShowImeWithHardKeyboard(mOriginalShowImeWithHardKeyboardEnabled); executeShellCommand("ime disable " + mInputMethodId); } @@ -170,7 +186,7 @@ public class InputMethodServiceTest { public void testShowHideKeyboard_byUserAction() { waitUntilActivityReadyForInputInjection(mActivity); - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); // Performs click on EditText to bring up the IME. Log.i(TAG, "Click on EditText"); @@ -201,14 +217,12 @@ public class InputMethodServiceTest { */ @Test public void testShowHideKeyboard_byInputMethodManager() { - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); - // Triggers to show IME via public API. verifyInputViewStatusOnMainSync( () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(), EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); - // Triggers to hide IME via public API. verifyInputViewStatusOnMainSync( () -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(), EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown"); @@ -219,14 +233,12 @@ public class InputMethodServiceTest { */ @Test public void testShowHideKeyboard_byInsetsController() { - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); - // Triggers to show IME via public API. verifyInputViewStatusOnMainSync( () -> mActivity.showImeWithWindowInsetsController(), EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); - // Triggers to hide IME via public API. verifyInputViewStatusOnMainSync( () -> mActivity.hideImeWithWindowInsetsController(), EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown"); @@ -234,53 +246,18 @@ public class InputMethodServiceTest { /** * This checks the result of calling IMS#requestShowSelf and IMS#requestHideSelf. - * - * <p>With the refactor in b/298172246, all calls to IMMS#{show,hide}MySoftInputLocked - * will be just apply the requested visibility (by using the callback). Therefore, we will - * lose flags like HIDE_IMPLICIT_ONLY. */ @Test public void testShowHideSelf() { - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); - // IME request to show itself without any flags, expect shown. - Log.i(TAG, "Call IMS#requestShowSelf(0)"); verifyInputViewStatusOnMainSync( () -> mInputMethodService.requestShowSelf(0 /* flags */), EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); - if (!mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) { - // IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect not hide (shown). - Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)"); - verifyInputViewStatusOnMainSync( - () -> mInputMethodService.requestHideSelf( - InputMethodManager.HIDE_IMPLICIT_ONLY), - EVENT_HIDE, false /* eventExpected */, true /* shown */, - "IME is still shown after HIDE_IMPLICIT_ONLY"); - } - - // IME request to hide itself without any flags, expect hidden. - Log.i(TAG, "Call IMS#requestHideSelf(0)"); verifyInputViewStatusOnMainSync( () -> mInputMethodService.requestHideSelf(0 /* flags */), EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown"); - - if (!mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) { - // IME request to show itself with flag SHOW_IMPLICIT, expect shown. - Log.i(TAG, "Call IMS#requestShowSelf(InputMethodManager.SHOW_IMPLICIT)"); - verifyInputViewStatusOnMainSync( - () -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT), - EVENT_SHOW, true /* eventExpected */, true /* shown */, - "IME is shown with SHOW_IMPLICIT"); - - // IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect hidden. - Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)"); - verifyInputViewStatusOnMainSync( - () -> mInputMethodService.requestHideSelf( - InputMethodManager.HIDE_IMPLICIT_ONLY), - EVENT_HIDE, true /* eventExpected */, false /* shown */, - "IME is not shown after HIDE_IMPLICIT_ONLY"); - } } /** @@ -289,28 +266,25 @@ public class InputMethodServiceTest { */ @Test public void testOnEvaluateInputViewShown_showImeWithHardKeyboard() { - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); final var config = mInputMethodService.getResources().getConfiguration(); final var initialConfig = new Configuration(config); try { config.keyboard = Configuration.KEYBOARD_QWERTY; config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; - eventually(() -> - assertWithMessage("InputView should show with visible hardware keyboard") - .that(mInputMethodService.onEvaluateInputViewShown()).isTrue()); + assertWithMessage("InputView should show with visible hardware keyboard") + .that(mInputMethodService.onEvaluateInputViewShown()).isTrue(); config.keyboard = Configuration.KEYBOARD_NOKEYS; config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; - eventually(() -> - assertWithMessage("InputView should show without hardware keyboard") - .that(mInputMethodService.onEvaluateInputViewShown()).isTrue()); + assertWithMessage("InputView should show without hardware keyboard") + .that(mInputMethodService.onEvaluateInputViewShown()).isTrue(); config.keyboard = Configuration.KEYBOARD_QWERTY; config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES; - eventually(() -> - assertWithMessage("InputView should show with hidden hardware keyboard") - .that(mInputMethodService.onEvaluateInputViewShown()).isTrue()); + assertWithMessage("InputView should show with hidden hardware keyboard") + .that(mInputMethodService.onEvaluateInputViewShown()).isTrue(); } finally { mInputMethodService.getResources() .updateConfiguration(initialConfig, null /* metrics */, null /* compat */); @@ -323,28 +297,25 @@ public class InputMethodServiceTest { */ @Test public void testOnEvaluateInputViewShown_disableShowImeWithHardKeyboard() { - setShowImeWithHardKeyboard(false /* enabled */); + setShowImeWithHardKeyboard(false /* enable */); final var config = mInputMethodService.getResources().getConfiguration(); final var initialConfig = new Configuration(config); try { config.keyboard = Configuration.KEYBOARD_QWERTY; config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; - eventually(() -> - assertWithMessage("InputView should not show with visible hardware keyboard") - .that(mInputMethodService.onEvaluateInputViewShown()).isFalse()); + assertWithMessage("InputView should not show with visible hardware keyboard") + .that(mInputMethodService.onEvaluateInputViewShown()).isFalse(); config.keyboard = Configuration.KEYBOARD_NOKEYS; config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; - eventually(() -> - assertWithMessage("InputView should show without hardware keyboard") - .that(mInputMethodService.onEvaluateInputViewShown()).isTrue()); + assertWithMessage("InputView should show without hardware keyboard") + .that(mInputMethodService.onEvaluateInputViewShown()).isTrue(); config.keyboard = Configuration.KEYBOARD_QWERTY; config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES; - eventually(() -> - assertWithMessage("InputView should show with hidden hardware keyboard") - .that(mInputMethodService.onEvaluateInputViewShown()).isTrue()); + assertWithMessage("InputView should show with hidden hardware keyboard") + .that(mInputMethodService.onEvaluateInputViewShown()).isTrue(); } finally { mInputMethodService.getResources() .updateConfiguration(initialConfig, null /* metrics */, null /* compat */); @@ -357,7 +328,7 @@ public class InputMethodServiceTest { */ @Test public void testShowSoftInput_disableShowImeWithHardKeyboard() { - setShowImeWithHardKeyboard(false /* enabled */); + setShowImeWithHardKeyboard(false /* enable */); final var config = mInputMethodService.getResources().getConfiguration(); final var initialConfig = new Configuration(config); @@ -386,49 +357,17 @@ public class InputMethodServiceTest { } /** - * This checks that an explicit show request results in the IME being shown. - */ - @Test - public void testShowSoftInputExplicitly() { - setShowImeWithHardKeyboard(true /* enabled */); - - // When InputMethodService#onEvaluateInputViewShown() returns true and flag is EXPLICIT, the - // IME should be shown. - verifyInputViewStatusOnMainSync( - () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(), - EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); - } - - /** - * This checks that an implicit show request results in the IME being shown. - */ - @Test - public void testShowSoftInputImplicitly() { - setShowImeWithHardKeyboard(true /* enabled */); - - // When InputMethodService#onEvaluateInputViewShown() returns true and flag is IMPLICIT, - // the IME should be shown. - verifyInputViewStatusOnMainSync(() -> assertThat( - mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(), - EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); - } - - /** * This checks that an explicit show request when the IME is not previously shown, * and it should be shown in fullscreen mode, results in the IME being shown. */ @Test public void testShowSoftInputExplicitly_fullScreenMode() { - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); // Set orientation landscape to enable fullscreen mode. setOrientation(2); - eventually(() -> assertWithMessage("No longer in natural orientation") - .that(mUiDevice.isNaturalOrientation()).isFalse()); - // Wait for the TestActivity to be recreated. eventually(() -> assertWithMessage("Activity was re-created after rotation") .that(TestActivity.getInstance()).isNotEqualTo(mActivity)); - // Get the new TestActivity. mActivity = TestActivity.getInstance(); assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull(); // Wait for the new EditText to be served by InputMethodManager. @@ -442,34 +381,40 @@ public class InputMethodServiceTest { /** * This checks that an implicit show request when the IME is not previously shown, - * and it should be shown in fullscreen mode, results in the IME not being shown. + * and it should be shown in fullscreen mode behaves like an explicit show request, resulting + * in the IME being shown. This is due to the refactor in b/298172246, causing us to lose flag + * information like {@link InputMethodManager#SHOW_IMPLICIT}. * - * <p>With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput - * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like - * SHOW_IMPLICIT. + * <p>Previously, an implicit show request when the IME is not previously shown, + * and it should be shown in fullscreen mode, would result in the IME not being shown. */ @Test - @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER) public void testShowSoftInputImplicitly_fullScreenMode() { - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); // Set orientation landscape to enable fullscreen mode. setOrientation(2); - eventually(() -> assertWithMessage("No longer in natural orientation") - .that(mUiDevice.isNaturalOrientation()).isFalse()); - // Wait for the TestActivity to be recreated. eventually(() -> assertWithMessage("Activity was re-created after rotation") .that(TestActivity.getInstance()).isNotEqualTo(mActivity)); - // Get the new TestActivity. mActivity = TestActivity.getInstance(); assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull(); // Wait for the new EditText to be served by InputMethodManager. eventually(() -> assertWithMessage("Has an input connection to the re-created Activity") .that(mImm.hasActiveInputConnection(mActivity.getEditText())).isTrue()); - verifyInputViewStatusOnMainSync(() -> assertThat( - mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(), - EVENT_SHOW, false /* eventExpected */, false /* shown */, "IME is not shown"); + if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) { + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager( + InputMethodManager.SHOW_IMPLICIT)) + .isTrue(), + EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); + } else { + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager( + InputMethodManager.SHOW_IMPLICIT)) + .isTrue(), + EVENT_SHOW, false /* eventExpected */, false /* shown */, "IME is not shown"); + } } /** @@ -478,7 +423,7 @@ public class InputMethodServiceTest { */ @Test public void testShowSoftInputExplicitly_withHardKeyboard() { - setShowImeWithHardKeyboard(false /* enabled */); + setShowImeWithHardKeyboard(false /* enable */); final var config = mInputMethodService.getResources().getConfiguration(); final var initialConfig = new Configuration(config); @@ -497,17 +442,17 @@ public class InputMethodServiceTest { } /** - * This checks that an implicit show request when a hardware keyboard is connected, - * results in the IME not being shown. + * This checks that an implicit show request when a hardware keyboard is connected behaves + * like an explicit show request, resulting in the IME being shown. This is due to the + * refactor in b/298172246, causing us to lose flag information like + * {@link InputMethodManager#SHOW_IMPLICIT}. * - * <p>With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput - * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like - * SHOW_IMPLICIT. + * <p>Previously, an implicit show request when a hardware keyboard is connected would + * result in the IME not being shown. */ @Test - @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER) public void testShowSoftInputImplicitly_withHardKeyboard() { - setShowImeWithHardKeyboard(false /* enabled */); + setShowImeWithHardKeyboard(false /* enable */); final var config = mInputMethodService.getResources().getConfiguration(); final var initialConfig = new Configuration(config); @@ -516,10 +461,20 @@ public class InputMethodServiceTest { config.keyboard = Configuration.KEYBOARD_QWERTY; config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES; - verifyInputViewStatusOnMainSync(() ->assertThat( - mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)) - .isTrue(), - EVENT_SHOW, false /* eventExpected */, false /* shown */, "IME is not shown"); + if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) { + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager( + InputMethodManager.SHOW_IMPLICIT)) + .isTrue(), + EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); + } else { + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager( + InputMethodManager.SHOW_IMPLICIT)) + .isTrue(), + EVENT_SHOW, false /* eventExpected */, false /* shown */, + "IME is not shown"); + } } finally { mInputMethodService.getResources() .updateConfiguration(initialConfig, null /* metrics */, null /* compat */); @@ -532,7 +487,7 @@ public class InputMethodServiceTest { */ @Test public void testShowSoftInputExplicitly_thenConfigurationChanged() { - setShowImeWithHardKeyboard(false /* enabled */); + setShowImeWithHardKeyboard(false /* enable */); final var config = mInputMethodService.getResources().getConfiguration(); final var initialConfig = new Configuration(config); @@ -565,17 +520,17 @@ public class InputMethodServiceTest { /** * This checks that an implicit show request followed by connecting a hardware keyboard - * and a configuration change, does not trigger IMS#onFinishInputView, - * but results in the IME being hidden. + * and a configuration change behaves like an explicit show request, resulting in the IME + * still being shown. This is due to the refactor in b/298172246, causing us to lose flag + * information like {@link InputMethodManager#SHOW_IMPLICIT}. * - * <p>With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput - * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like - * SHOW_IMPLICIT. + * <p>Previously, an implicit show request followed by connecting a hardware keyboard + * and a configuration change, would not trigger IMS#onFinishInputView, but resulted in the + * IME being hidden. */ @Test - @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER) public void testShowSoftInputImplicitly_thenConfigurationChanged() { - setShowImeWithHardKeyboard(false /* enabled */); + setShowImeWithHardKeyboard(false /* enable */); final var config = mInputMethodService.getResources().getConfiguration(); final var initialConfig = new Configuration(config); @@ -596,16 +551,23 @@ public class InputMethodServiceTest { // Simulate a fake configuration change to avoid the recreation of TestActivity. config.orientation = Configuration.ORIENTATION_LANDSCAPE; - // Normally, IMS#onFinishInputView will be called when finishing the input view by - // the user. But if IMS#hideWindow is called when receiving a new configuration change, - // we don't expect that it's user-driven to finish the lifecycle of input view with - // IMS#onFinishInputView, because the input view will be re-initialized according - // to the last #mShowInputRequested state. So in this case we treat the input view as - // still alive. - verifyInputViewStatusOnMainSync( - () -> mInputMethodService.onConfigurationChanged(config), - EVENT_CONFIG, true /* eventExpected */, true /* inputViewStarted */, - false /* shown */, "IME is not shown after a configuration change"); + if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) { + verifyInputViewStatusOnMainSync( + () -> mInputMethodService.onConfigurationChanged(config), + EVENT_CONFIG, true /* eventExpected */, true /* shown */, + "IME is still shown after a configuration change"); + } else { + // Normally, IMS#onFinishInputView will be called when finishing the input view by + // the user. But if IMS#hideWindow is called when receiving a new configuration + // change, we don't expect that it's user-driven to finish the lifecycle of input + // view with IMS#onFinishInputView, because the input view will be re-initialized + // according to the last #mShowInputRequested state. So in this case we treat the + // input view as still alive. + verifyInputViewStatusOnMainSync( + () -> mInputMethodService.onConfigurationChanged(config), + EVENT_CONFIG, true /* eventExpected */, true /* inputViewStarted */, + false /* shown */, "IME is not shown after a configuration change"); + } } finally { mInputMethodService.getResources() .updateConfiguration(initialConfig, null /* metrics */, null /* compat */); @@ -619,7 +581,7 @@ public class InputMethodServiceTest { */ @Test public void testShowSoftInputExplicitly_thenShowSoftInputImplicitly_withHardKeyboard() { - setShowImeWithHardKeyboard(false /* enabled */); + setShowImeWithHardKeyboard(false /* enable */); final var config = mInputMethodService.getResources().getConfiguration(); final var initialConfig = new Configuration(config); @@ -628,12 +590,10 @@ public class InputMethodServiceTest { config.keyboard = Configuration.KEYBOARD_QWERTY; config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES; - // Explicit show request. verifyInputViewStatusOnMainSync(() -> assertThat( mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(), EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); - // Implicit show request. verifyInputViewStatusOnMainSync(() -> assertThat( mActivity.showImeWithInputMethodManager( InputMethodManager.SHOW_IMPLICIT)).isTrue(), @@ -654,17 +614,18 @@ public class InputMethodServiceTest { /** * This checks that a forced show request directly followed by an explicit show request, - * and then a hide not always request, still results in the IME being shown - * (i.e. the explicit show request retains the forced state). + * and then a not always hide request behaves like a normal hide request, resulting in the + * IME being hidden (i.e. the explicit show request does not retain the forced state). This is + * due to the refactor in b/298172246, causing us to lose flag information like + * {@link InputMethodManager#SHOW_FORCED}. * - * <p>With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput - * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like - * HIDE_NOT_ALWAYS. + * <p>Previously, a forced show request directly followed by an explicit show request, + * and then a not always hide request, would result in the IME still being shown + * (i.e. the explicit show request would retain the forced state). */ @Test - @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER) public void testShowSoftInputForced_testShowSoftInputExplicitly_thenHideSoftInputNotAlways() { - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); verifyInputViewStatusOnMainSync(() -> assertThat( mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_FORCED)).isTrue(), @@ -674,11 +635,123 @@ public class InputMethodServiceTest { mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(), EVENT_SHOW, false /* eventExpected */, true /* shown */, "IME is still shown"); - verifyInputViewStatusOnMainSync(() -> assertThat( - mActivity.hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS)) - .isTrue(), - EVENT_HIDE, false /* eventExpected */, true /* shown */, - "IME is still shown after HIDE_NOT_ALWAYS"); + if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) { + verifyInputViewStatusOnMainSync(() -> assertThat(mActivity + .hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS)) + .isTrue(), + EVENT_HIDE, true /* eventExpected */, false /* shown */, + "IME is not shown after HIDE_NOT_ALWAYS"); + } else { + verifyInputViewStatusOnMainSync(() -> assertThat(mActivity + .hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS)) + .isTrue(), + EVENT_HIDE, false /* eventExpected */, true /* shown */, + "IME is still shown after HIDE_NOT_ALWAYS"); + } + } + + /** + * This checks that an explicit show request followed by an implicit only hide request + * behaves like a normal hide request, resulting in the IME being hidden. This is due to + * the refactor in b/298172246, causing us to lose flag information like + * {@link InputMethodManager#SHOW_IMPLICIT} and {@link InputMethodManager#HIDE_IMPLICIT_ONLY}. + * + * <p>Previously, an explicit show request followed by an implicit only hide request + * would result in the IME still being shown. + */ + @Test + public void testShowSoftInputExplicitly_thenHideSoftInputImplicitOnly() { + setShowImeWithHardKeyboard(true /* enable */); + + verifyInputViewStatusOnMainSync( + () -> mActivity.showImeWithInputMethodManager(0 /* flags */), + EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); + + if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) { + verifyInputViewStatusOnMainSync( + () -> mActivity.hideImeWithInputMethodManager( + InputMethodManager.HIDE_IMPLICIT_ONLY), + EVENT_HIDE, true /* eventExpected */, false /* shown */, + "IME is not shown after HIDE_IMPLICIT_ONLY"); + } else { + verifyInputViewStatusOnMainSync( + () -> mActivity.hideImeWithInputMethodManager( + InputMethodManager.HIDE_IMPLICIT_ONLY), + EVENT_HIDE, false /* eventExpected */, true /* shown */, + "IME is still shown after HIDE_IMPLICIT_ONLY"); + } + } + + /** + * This checks that an implicit show request followed by an implicit only hide request + * results in the IME being hidden. + */ + @Test + public void testShowSoftInputImplicitly_thenHideSoftInputImplicitOnly() { + setShowImeWithHardKeyboard(true /* enable */); + + verifyInputViewStatusOnMainSync( + () -> mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT), + EVENT_SHOW, true /* eventExpected */, true /* shown */, + "IME is shown with SHOW_IMPLICIT"); + + verifyInputViewStatusOnMainSync( + () -> mActivity.hideImeWithInputMethodManager( + InputMethodManager.HIDE_IMPLICIT_ONLY), + EVENT_HIDE, true /* eventExpected */, false /* shown */, + "IME is not shown after HIDE_IMPLICIT_ONLY"); + } + + /** + * This checks that an explicit show self request followed by an implicit only hide self request + * behaves like a normal hide self request, resulting in the IME being hidden. This is due to + * the refactor in b/298172246, causing us to lose flag information like + * {@link InputMethodManager#SHOW_IMPLICIT} and {@link InputMethodManager#HIDE_IMPLICIT_ONLY}. + * + * <p>Previously, an explicit show self request followed by an implicit only hide self request + * would result in the IME still being shown. + */ + @Test + public void testShowSelfExplicitly_thenHideSelfImplicitOnly() { + setShowImeWithHardKeyboard(true /* enable */); + + verifyInputViewStatusOnMainSync( + () -> mInputMethodService.requestShowSelf(0 /* flags */), + EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); + + if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) { + verifyInputViewStatusOnMainSync( + () -> mInputMethodService.requestHideSelf( + InputMethodManager.HIDE_IMPLICIT_ONLY), + EVENT_HIDE, true /* eventExpected */, false /* shown */, + "IME is not shown after HIDE_IMPLICIT_ONLY"); + } else { + verifyInputViewStatusOnMainSync( + () -> mInputMethodService.requestHideSelf( + InputMethodManager.HIDE_IMPLICIT_ONLY), + EVENT_HIDE, false /* eventExpected */, true /* shown */, + "IME is still shown after HIDE_IMPLICIT_ONLY"); + } + } + + /** + * This checks that an implicit show self request followed by an implicit only hide self request + * results in the IME being hidden. + */ + @Test + public void testShowSelfImplicitly_thenHideSelfImplicitOnly() { + setShowImeWithHardKeyboard(true /* enable */); + + verifyInputViewStatusOnMainSync( + () -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT), + EVENT_SHOW, true /* eventExpected */, true /* shown */, + "IME is shown with SHOW_IMPLICIT"); + + verifyInputViewStatusOnMainSync( + () -> mInputMethodService.requestHideSelf( + InputMethodManager.HIDE_IMPLICIT_ONLY), + EVENT_HIDE, true /* eventExpected */, false /* shown */, + "IME is not shown after HIDE_IMPLICIT_ONLY"); } /** @@ -686,7 +759,7 @@ public class InputMethodServiceTest { */ @Test public void testFullScreenMode() { - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); Log.i(TAG, "Set orientation natural"); verifyFullscreenMode(() -> setOrientation(0), false /* eventExpected */, @@ -723,25 +796,22 @@ public class InputMethodServiceTest { public void testShowHideImeNavigationBar_doesDrawImeNavBar() { assumeTrue("Must have a navigation bar", hasNavigationBar()); - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); - // Show IME verifyInputViewStatusOnMainSync( () -> { - setDrawsImeNavBarAndSwitcherButton(true /* enabled */); + setDrawsImeNavBarAndSwitcherButton(true /* enable */); mActivity.showImeWithWindowInsetsController(); }, EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); assertWithMessage("IME navigation bar is initially shown") .that(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue(); - // Try to hide IME nav bar mInstrumentation.runOnMainSync(() -> setShowImeNavigationBar(false /* show */)); mInstrumentation.waitForIdleSync(); assertWithMessage("IME navigation bar is not shown after hide request") .that(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse(); - // Try to show IME nav bar mInstrumentation.runOnMainSync(() -> setShowImeNavigationBar(true /* show */)); mInstrumentation.waitForIdleSync(); assertWithMessage("IME navigation bar is shown after show request") @@ -758,25 +828,22 @@ public class InputMethodServiceTest { public void testShowHideImeNavigationBar_doesNotDrawImeNavBar() { assumeTrue("Must have a navigation bar", hasNavigationBar()); - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); - // Show IME verifyInputViewStatusOnMainSync( () -> { - setDrawsImeNavBarAndSwitcherButton(false /* enabled */); + setDrawsImeNavBarAndSwitcherButton(false /* enable */); mActivity.showImeWithWindowInsetsController(); }, EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); assertWithMessage("IME navigation bar is initially not shown") .that(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse(); - // Try to hide IME nav bar mInstrumentation.runOnMainSync(() -> setShowImeNavigationBar(false /* show */)); mInstrumentation.waitForIdleSync(); assertWithMessage("IME navigation bar is not shown after hide request") .that(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse(); - // Try to show IME nav bar mInstrumentation.runOnMainSync(() -> setShowImeNavigationBar(true /* show */)); mInstrumentation.waitForIdleSync(); assertWithMessage("IME navigation bar is not shown after show request") @@ -792,13 +859,16 @@ public class InputMethodServiceTest { waitUntilActivityReadyForInputInjection(mActivity); - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) { verifyInputViewStatusOnMainSync( () -> mActivity.showImeWithWindowInsetsController(), EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); + eventually(() -> assertWithMessage("IME navigation bar is shown") + .that(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue()); + final var backButton = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID)); verifyInputViewStatus( () -> { @@ -818,13 +888,16 @@ public class InputMethodServiceTest { waitUntilActivityReadyForInputInjection(mActivity); - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) { verifyInputViewStatusOnMainSync( () -> mActivity.showImeWithWindowInsetsController(), EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); + eventually(() -> assertWithMessage("IME navigation bar is shown") + .that(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue()); + final var backButton = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID)); verifyInputViewStatus( () -> { @@ -844,7 +917,7 @@ public class InputMethodServiceTest { waitUntilActivityReadyForInputInjection(mActivity); - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); final var info = mImm.getCurrentInputMethodInfo(); assertWithMessage("InputMethodInfo is not null").that(info).isNotNull(); @@ -855,11 +928,14 @@ public class InputMethodServiceTest { try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) { verifyInputViewStatusOnMainSync( () -> { - setDrawsImeNavBarAndSwitcherButton(true /* enabled */); + setDrawsImeNavBarAndSwitcherButton(true /* enable */); mActivity.showImeWithWindowInsetsController(); }, EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); + eventually(() -> assertWithMessage("IME navigation bar is shown") + .that(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue()); + final var imeSwitcherButton = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID)); imeSwitcherButton.click(); mInstrumentation.waitForIdleSync(); @@ -884,7 +960,7 @@ public class InputMethodServiceTest { waitUntilActivityReadyForInputInjection(mActivity); - setShowImeWithHardKeyboard(true /* enabled */); + setShowImeWithHardKeyboard(true /* enable */); final var info = mImm.getCurrentInputMethodInfo(); assertWithMessage("InputMethodInfo is not null").that(info).isNotNull(); @@ -893,11 +969,14 @@ public class InputMethodServiceTest { try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) { verifyInputViewStatusOnMainSync( () -> { - setDrawsImeNavBarAndSwitcherButton(true /* enabled */); + setDrawsImeNavBarAndSwitcherButton(true /* enable */); mActivity.showImeWithWindowInsetsController(); }, EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); + eventually(() -> assertWithMessage("IME navigation bar is shown") + .that(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue()); + final var imeSwitcherButton = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID)); imeSwitcherButton.longClick(); mInstrumentation.waitForIdleSync(); @@ -956,7 +1035,8 @@ public class InputMethodServiceTest { runnable.run(); } mInstrumentation.waitForIdleSync(); - eventCalled = latch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS); + eventCalled = latch.await(eventExpected ? EXPECT_TIMEOUT_MS : NOT_EXCEPT_TIMEOUT_MS, + TimeUnit.MILLISECONDS); } catch (InterruptedException e) { fail("Interrupted while waiting for latch: " + e.getMessage()); return; @@ -1016,10 +1096,8 @@ public class InputMethodServiceTest { verifyInputViewStatus(runnable, EVENT_CONFIG, eventExpected, false /* shown */, "IME is not shown"); if (eventExpected) { - // Wait for the TestActivity to be recreated. eventually(() -> assertWithMessage("Activity was re-created after rotation") .that(TestActivity.getInstance()).isNotEqualTo(mActivity)); - // Get the new TestActivity. mActivity = TestActivity.getInstance(); assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull(); // Wait for the new EditText to be served by InputMethodManager. @@ -1062,6 +1140,7 @@ public class InputMethodServiceTest { private void prepareActivity() { mActivity = TestActivity.startSync(mInstrumentation); + mInstrumentation.waitForIdleSync(); Log.i(TAG, "Finish preparing activity with editor."); } @@ -1086,21 +1165,51 @@ public class InputMethodServiceTest { * @param enable the value to be set. */ private void setShowImeWithHardKeyboard(boolean enable) { + if (mInputMethodService == null) { + // If the IME is no longer around, reset the setting unconditionally. + executeShellCommand(SET_SHOW_IME_WITH_HARD_KEYBOARD_CMD + " " + (enable ? "1" : "0")); + return; + } + final boolean currentEnabled = mInputMethodService.getShouldShowImeWithHardKeyboardForTesting(); if (currentEnabled != enable) { - executeShellCommand(enable - ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD - : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD); + executeShellCommand(SET_SHOW_IME_WITH_HARD_KEYBOARD_CMD + " " + (enable ? "1" : "0")); eventually(() -> assertWithMessage("showImeWithHardKeyboard updated") .that(mInputMethodService.getShouldShowImeWithHardKeyboardForTesting()) .isEqualTo(enable)); } } - private static void executeShellCommand(@NonNull String cmd) { + /** + * Gets the verbose logging state in {@link android.view.inputmethod.ImeTracker}. + * + * @return {@code true} iff verbose logging is enabled. + */ + private static boolean getVerboseImeTrackerLogging() { + return executeShellCommand(GET_VERBOSE_IME_TRACKER_LOGGING_CMD).trim().equals("1"); + } + + /** + * Sets verbose logging in {@link android.view.inputmethod.ImeTracker}. + * + * @param enabled whether to enable or disable verbose logging. + * + * @implNote This must use {@link ActivityManager#notifySystemPropertiesChanged()} to listen + * for changes to the system property for the verbose ImeTracker logging. + */ + private void setVerboseImeTrackerLogging(boolean enabled) { + final var context = mInstrumentation.getContext(); + final var am = context.getSystemService(ActivityManager.class); + + executeShellCommand(SET_VERBOSE_IME_TRACKER_LOGGING_CMD + " " + (enabled ? "1" : "0")); + am.notifySystemPropertiesChanged(); + } + + @NonNull + private static String executeShellCommand(@NonNull String cmd) { Log.i(TAG, "Run command: " + cmd); - SystemUtil.runShellCommandOrThrow(cmd); + return SystemUtil.runShellCommandOrThrow(cmd); } /** @@ -1113,8 +1222,7 @@ public class InputMethodServiceTest { @NonNull private UiObject2 getUiObject(@NonNull BySelector bySelector) { - final var uiObject = mUiDevice.wait(Until.findObject(bySelector), - TimeUnit.SECONDS.toMillis(TIMEOUT_IN_SECONDS)); + final var uiObject = mUiDevice.wait(Until.findObject(bySelector), TIMEOUT_MS); assertWithMessage("UiObject with " + bySelector + " was found").that(uiObject).isNotNull(); return uiObject; } @@ -1137,10 +1245,10 @@ public class InputMethodServiceTest { * * <p>Note, neither of these are normally drawn when in three button navigation mode. * - * @param enabled whether the IME nav bar and IME Switcher button are drawn. + * @param enable whether the IME nav bar and IME Switcher button are drawn. */ - private void setDrawsImeNavBarAndSwitcherButton(boolean enabled) { - final int flags = enabled ? IME_DRAWS_IME_NAV_BAR | SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0; + private void setDrawsImeNavBarAndSwitcherButton(boolean enable) { + final int flags = enable ? IME_DRAWS_IME_NAV_BAR | SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0; mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged(flags); } diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java index 558d1a7c4e8b..d4d4dcaa4f48 100644 --- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java +++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java @@ -111,12 +111,6 @@ public class InputMethodServiceWrapper extends InputMethodService { } @Override - public void requestHideSelf(int flags) { - Log.i(TAG, "requestHideSelf() " + flags); - super.requestHideSelf(flags); - } - - @Override public void onConfigurationChanged(Configuration newConfig) { Log.i(TAG, "onConfigurationChanged() " + newConfig); super.onConfigurationChanged(newConfig); diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java index f5c0de034483..e1c65d27459e 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java @@ -65,10 +65,13 @@ import android.content.pm.SigningDetails; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArraySet; import androidx.annotation.Nullable; @@ -150,6 +153,9 @@ public class PackageParserTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private File mTmpDir; private static final File FRAMEWORK = new File("/system/framework/framework-res.apk"); private static final String TEST_APP1_APK = "PackageParserTestApp1.apk"; @@ -846,7 +852,42 @@ public class PackageParserTest { @Test @RequiresFlagsEnabled(android.content.res.Flags.FLAG_MANIFEST_FLAGGING) - public void testParseWithFeatureFlagAttributes() throws Exception { + @DisableFlags(android.content.res.Flags.FLAG_USE_NEW_ACONFIG_STORAGE) + public void testParseWithFeatureFlagAttributes_oldStorage() throws Exception { + final File testFile = extractFile(TEST_APP8_APK); + try (PackageParser2 parser = new TestPackageParser2()) { + Map<String, Boolean> flagValues = new HashMap<>(); + flagValues.put("my.flag1", true); + flagValues.put("my.flag2", false); + flagValues.put("my.flag3", false); + flagValues.put("my.flag4", true); + ParsingPackageUtils.getAconfigFlags().addFlagValuesForTesting(flagValues); + + // The manifest has: + // <permission android:name="PERM1" android:featureFlag="my.flag1 " /> + // <permission android:name="PERM2" android:featureFlag=" !my.flag2" /> + // <permission android:name="PERM3" android:featureFlag="my.flag3" /> + // <permission android:name="PERM4" android:featureFlag="!my.flag4" /> + // <permission android:name="PERM5" android:featureFlag="unknown.flag" /> + // Therefore with the above flag values, only PERM1 and PERM2 should be present. + + final ParsedPackage pkg = parser.parsePackage(testFile, 0, false); + List<String> permissionNames = + pkg.getPermissions().stream().map(ParsedComponent::getName).toList(); + assertThat(permissionNames).contains(PACKAGE_NAME + ".PERM1"); + assertThat(permissionNames).contains(PACKAGE_NAME + ".PERM2"); + assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM3"); + assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM4"); + assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM5"); + } finally { + testFile.delete(); + } + } + + @Test + @RequiresFlagsEnabled(android.content.res.Flags.FLAG_MANIFEST_FLAGGING) + @EnableFlags(android.content.res.Flags.FLAG_USE_NEW_ACONFIG_STORAGE) + public void testParseWithFeatureFlagAttributes_newStorage() throws Exception { final File testFile = extractFile(TEST_APP8_APK); try (PackageParser2 parser = new TestPackageParser2()) { Map<String, Boolean> flagValues = new HashMap<>(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 7d25acd7f5e7..a42116351c2d 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -152,7 +152,7 @@ public class AutomaticBrightnessControllerTest { } @Override - AutomaticBrightnessController.Clock createClock(boolean isEnabled) { + AutomaticBrightnessController.Clock createClock() { return new AutomaticBrightnessController.Clock() { @Override public long uptimeMillis() { @@ -618,39 +618,46 @@ public class AutomaticBrightnessControllerTest { long increment = 500; // set autobrightness to low // t = 0 - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); // t = 500 mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); // t = 1000 mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(0.0f, mController.getAmbientLux(), EPSILON); // t = 1500 mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(0.0f, mController.getAmbientLux(), EPSILON); // t = 2000 // ensure that our reading is at 0. mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(0.0f, mController.getAmbientLux(), EPSILON); // t = 2500 // first 10000 lux sensor event reading mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertTrue(mController.getAmbientLux() > 0.0f); assertTrue(mController.getAmbientLux() < 10000.0f); // t = 3000 // lux reading should still not yet be 10000. mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertTrue(mController.getAmbientLux() > 0.0f); assertTrue(mController.getAmbientLux() < 10000.0f); @@ -659,45 +666,53 @@ public class AutomaticBrightnessControllerTest { // lux has been high (10000) for 1000ms. // lux reading should be 10000 // short horizon (ambient lux) is high, long horizon is still not high - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); // t = 4000 // stay high mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); // t = 4500 Mockito.clearInvocations(mBrightnessMappingStrategy); mClock.fastForward(increment); // short horizon is high, long horizon is high too - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); verify(mBrightnessMappingStrategy, times(1)).getBrightness(10000, null, -1); assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); // t = 5000 mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertTrue(mController.getAmbientLux() > 0.0f); assertTrue(mController.getAmbientLux() < 10000.0f); // t = 5500 mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertTrue(mController.getAmbientLux() > 0.0f); assertTrue(mController.getAmbientLux() < 10000.0f); // t = 6000 mClock.fastForward(increment); // ambient lux goes to 0 - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(0.0f, mController.getAmbientLux(), EPSILON); // only the values within the horizon should be kept assertArrayEquals(new float[] {10000, 10000, 0, 0, 0}, mController.getLastSensorValues(), EPSILON); - assertArrayEquals(new long[] {4000, 4500, 5000, 5500, 6000}, + assertArrayEquals(new long[]{4000 + ANDROID_SLEEP_TIME, 4500 + ANDROID_SLEEP_TIME, + 5000 + ANDROID_SLEEP_TIME, 5500 + ANDROID_SLEEP_TIME, + 6000 + ANDROID_SLEEP_TIME}, mController.getLastSensorTimestamps()); } @@ -793,7 +808,8 @@ public class AutomaticBrightnessControllerTest { for (int i = 0; i < 1000; i++) { lux += increment; mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); } int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment + 1); @@ -807,17 +823,17 @@ public class AutomaticBrightnessControllerTest { long sensorTimestamp = mClock.now(); for (int i = valuesCount - 1; i >= 1; i--) { assertEquals(lux, sensorValues[i], EPSILON); - assertEquals(sensorTimestamp, sensorTimestamps[i]); + assertEquals(sensorTimestamp + ANDROID_SLEEP_TIME, sensorTimestamps[i]); lux -= increment; sensorTimestamp -= increment; } assertEquals(lux, sensorValues[0], EPSILON); - assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]); + assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG + ANDROID_SLEEP_TIME, + sensorTimestamps[0]); } @Test public void testAmbientLuxBuffers_prunedBeyondLongHorizonExceptLatestValue() throws Exception { - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), @@ -867,7 +883,8 @@ public class AutomaticBrightnessControllerTest { for (int i = 0; i < 20; i++) { lux += increment1; mClock.fastForward(increment1); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); } int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment1 + 1); @@ -877,7 +894,8 @@ public class AutomaticBrightnessControllerTest { for (int i = 0; i < initialCapacity - valuesCount; i++) { lux += increment2; mClock.fastForward(increment2); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); } float[] sensorValues = mController.getLastSensorValues(); @@ -890,7 +908,7 @@ public class AutomaticBrightnessControllerTest { long sensorTimestamp = mClock.now(); for (int i = initialCapacity - 1; i >= 1; i--) { assertEquals(lux, sensorValues[i], EPSILON); - assertEquals(sensorTimestamp, sensorTimestamps[i]); + assertEquals(sensorTimestamp + ANDROID_SLEEP_TIME, sensorTimestamps[i]); if (i >= valuesCount) { lux -= increment2; @@ -901,7 +919,8 @@ public class AutomaticBrightnessControllerTest { } } assertEquals(lux, sensorValues[0], EPSILON); - assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]); + assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG + ANDROID_SLEEP_TIME, + sensorTimestamps[0]); } @Test @@ -951,25 +970,29 @@ public class AutomaticBrightnessControllerTest { // t = 0 // Initial lux - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(500, mController.getAmbientLux(), EPSILON); // t = 1000 // Lux isn't steady yet mClock.fastForward(1000); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(500, mController.getAmbientLux(), EPSILON); // t = 1500 // Lux isn't steady yet mClock.fastForward(500); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(500, mController.getAmbientLux(), EPSILON); // t = 2500 // Lux is steady now mClock.fastForward(1000); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(1200, mController.getAmbientLux(), EPSILON); } @@ -992,25 +1015,29 @@ public class AutomaticBrightnessControllerTest { // t = 0 // Initial lux - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(1200, mController.getAmbientLux(), EPSILON); // t = 2000 // Lux isn't steady yet mClock.fastForward(2000); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(1200, mController.getAmbientLux(), EPSILON); // t = 2500 // Lux isn't steady yet mClock.fastForward(500); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(1200, mController.getAmbientLux(), EPSILON); // t = 4500 // Lux is steady now mClock.fastForward(2000); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(500, mController.getAmbientLux(), EPSILON); } @@ -1031,19 +1058,22 @@ public class AutomaticBrightnessControllerTest { // t = 0 // Initial lux - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(500, mController.getAmbientLux(), EPSILON); // t = 500 // Lux isn't steady yet mClock.fastForward(500); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(500, mController.getAmbientLux(), EPSILON); // t = 1500 // Lux is steady now mClock.fastForward(1000); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(1200, mController.getAmbientLux(), EPSILON); } @@ -1068,19 +1098,22 @@ public class AutomaticBrightnessControllerTest { // t = 0 // Initial lux - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(1200, mController.getAmbientLux(), EPSILON); // t = 1000 // Lux isn't steady yet mClock.fastForward(1000); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(1200, mController.getAmbientLux(), EPSILON); // t = 2500 // Lux is steady now mClock.fastForward(1500); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(500, mController.getAmbientLux(), EPSILON); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index aed1f9858660..db94958f769e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -1066,7 +1066,6 @@ public final class DisplayPowerControllerTest { com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true); mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true); - when(mDisplayManagerFlagsMock.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); @@ -1172,7 +1171,6 @@ public final class DisplayPowerControllerTest { com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true); mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true); - when(mDisplayManagerFlagsMock.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(false); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java index 7d3cd8a8a9ae..38de7ce013c2 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -34,6 +34,7 @@ import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REM import static com.android.server.display.DisplayDeviceInfo.DIFF_EVERYTHING; import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED; +import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CONNECTED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DISCONNECTED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED; @@ -1180,13 +1181,21 @@ public class LogicalDisplayMapperTest { assertEquals(LOGICAL_DISPLAY_EVENT_STATE_CHANGED, mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo)); + // Change the display committed state + when(mFlagsMock.isCommittedStateSeparateEventEnabled()).thenReturn(true); + newDisplayInfo = new DisplayInfo(); + newDisplayInfo.committedState = STATE_OFF; + assertEquals(LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED, + mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo)); // Change multiple properties newDisplayInfo = new DisplayInfo(); newDisplayInfo.refreshRateOverride = 30; newDisplayInfo.state = STATE_OFF; + newDisplayInfo.committedState = STATE_OFF; assertEquals(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED - | LOGICAL_DISPLAY_EVENT_STATE_CHANGED, + | LOGICAL_DISPLAY_EVENT_STATE_CHANGED + | LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED, mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo)); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java index 2ebb6c2a3ce4..ef39167dbabc 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java @@ -240,7 +240,6 @@ public final class DisplayBrightnessStrategySelectorTest { @Test public void selectStrategyDoesNotSelectDozeStrategyWhenOffloadSessionAutoBrightnessIsEnabled() { - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( @@ -378,7 +377,6 @@ public final class DisplayBrightnessStrategySelectorTest { @Test public void selectStrategy_selectsAutomaticStrategyWhenValid() { when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( @@ -409,7 +407,6 @@ public final class DisplayBrightnessStrategySelectorTest { @Test public void selectStrategy_doesNotSelectAutomaticStrategyWhenStylusInUse() { when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( @@ -536,7 +533,6 @@ public final class DisplayBrightnessStrategySelectorTest { @Test public void setAllowAutoBrightnessWhileDozing_enabledWhenConfigAndOffloadSessionAreEnabled() { - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( @@ -550,7 +546,6 @@ public final class DisplayBrightnessStrategySelectorTest { @Test public void setAllowAutoBrightnessWhileDozing_disabledWhenOffloadSessionFlagIsDisabled() { - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(false); when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( @@ -564,7 +559,6 @@ public final class DisplayBrightnessStrategySelectorTest { @Test public void setAllowAutoBrightnessWhileDozing_disabledWhenABWhileDozingConfigIsDisabled() { - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( @@ -588,7 +582,6 @@ public final class DisplayBrightnessStrategySelectorTest { @Test public void setAllowAutoBrightnessWhileDozing_EnabledWhenFlagsAreDisabled() { - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( true); mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, @@ -600,11 +593,5 @@ public final class DisplayBrightnessStrategySelectorTest { mDisplayBrightnessStrategySelector .setAllowAutoBrightnessWhileDozing(mDisplayOffloadSession); assertTrue(mDisplayBrightnessStrategySelector.isAllowAutoBrightnessWhileDozing()); - - when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(false); - mDisplayBrightnessStrategySelector - .setAllowAutoBrightnessWhileDozing(mDisplayOffloadSession); - assertTrue(mDisplayBrightnessStrategySelector.isAllowAutoBrightnessWhileDozing()); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index db04d39e772c..eda5e8613dba 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -23,7 +23,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -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.PackageWatchdog.MITIGATION_RESULT_SKIPPED; import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS; @@ -34,8 +33,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import android.content.ContentResolver; @@ -43,13 +40,8 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; -import android.crashrecovery.flags.Flags; import android.os.RecoverySystem; import android.os.SystemProperties; -import android.os.UserHandle; -import android.platform.test.annotations.EnableFlags; -import android.platform.test.flag.junit.FlagsParameterization; -import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.provider.Settings; @@ -60,14 +52,8 @@ import com.android.server.am.SettingsToPropertiesMapper; import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; @@ -76,34 +62,19 @@ import org.mockito.stubbing.Answer; import java.lang.reflect.Field; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.concurrent.TimeUnit; /** * Test RescueParty. */ -@RunWith(Parameterized.class) public class RescuePartyTest { - @Rule - public SetFlagsRule mSetFlagsRule; private static final long CURRENT_NETWORK_TIME_MILLIS = 0L; - private static final String FAKE_NATIVE_NAMESPACE1 = "native1"; - private static final String FAKE_NATIVE_NAMESPACE2 = "native2"; - private static final String[] FAKE_RESET_NATIVE_NAMESPACES = - {FAKE_NATIVE_NAMESPACE1, FAKE_NATIVE_NAMESPACE2}; private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1); private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; - private static final String CALLING_PACKAGE1 = "com.package.name1"; - private static final String CALLING_PACKAGE2 = "com.package.name2"; - private static final String CALLING_PACKAGE3 = "com.package.name3"; private static final String PERSISTENT_PACKAGE = "com.persistent.package"; private static final String NON_PERSISTENT_PACKAGE = "com.nonpersistent.package"; - private static final String NAMESPACE1 = "namespace1"; - private static final String NAMESPACE2 = "namespace2"; - private static final String NAMESPACE3 = "namespace3"; - private static final String NAMESPACE4 = "namespace4"; private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG = "persist.device_config.configuration.disable_rescue_party"; private static final String PROP_DISABLE_FACTORY_RESET_FLAG = @@ -127,22 +98,6 @@ public class RescuePartyTest { // Mock only sysprop apis private PackageWatchdog.BootThreshold mSpyBootThreshold; - @Captor - private ArgumentCaptor<DeviceConfig.MonitorCallback> mMonitorCallbackCaptor; - @Captor - private ArgumentCaptor<List<String>> mPackageListCaptor; - - @Parameters(name = "{0}") - public static List<FlagsParameterization> getFlags() { - return FlagsParameterization.allCombinationsOf( - Flags.FLAG_RECOVERABILITY_DETECTION, - Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS); - } - - public RescuePartyTest(FlagsParameterization flags) { - mSetFlagsRule = new SetFlagsRule(flags); - } - @Before public void setUp() throws Exception { mSession = @@ -248,7 +203,6 @@ public class RescuePartyTest { } @Test - @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS) public void testBootLoopNoFlags() { // this is old test where the flag needs to be disabled noteBoot(1); @@ -260,7 +214,6 @@ public class RescuePartyTest { } @Test - @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS) public void testPersistentAppCrashNoFlags() { // this is old test where the flag needs to be disabled noteAppCrash(1, true); @@ -396,7 +349,6 @@ public class RescuePartyTest { } @Test - @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS) public void testHealthCheckLevelsNoFlags() { // this is old test where the flag needs to be disabled RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); @@ -416,7 +368,6 @@ public class RescuePartyTest { } @Test - @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS) public void testBootLoopLevelsNoFlags() { RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); @@ -425,25 +376,6 @@ public class RescuePartyTest { } - private void verifySettingsResets(int resetMode, String[] resetNamespaces, - HashMap<String, Integer> configResetVerifiedTimesMap) { - verifyOnlySettingsReset(resetMode); - } - - private void verifyOnlySettingsReset(int resetMode) { - verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null, - resetMode, UserHandle.USER_SYSTEM)); - verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(), - eq(resetMode), anyInt())); - } - - private void verifyNoSettingsReset(int resetMode) { - verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null, - resetMode, UserHandle.USER_SYSTEM), never()); - verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(), - eq(resetMode), anyInt()), never()); - } - private void noteBoot(int mitigationCount) { RescuePartyObserver.getInstance(mMockContext).onExecuteBootLoopMitigation(mitigationCount); } diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java index 5ddd8a50135b..2e315ecd7b37 100644 --- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java @@ -26,7 +26,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.app.role.RoleManager; +import android.companion.AssociationRequest; import android.content.pm.PackageManagerInternal; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; @@ -54,6 +57,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; import java.util.Set; @SmallTest @@ -74,6 +78,7 @@ public class SensitiveContentProtectionManagerServiceContentTest { @Mock private WindowManagerInternal mWindowManager; @Mock private MediaProjectionManager mProjectionManager; @Mock private PackageManagerInternal mPackageManagerInternal; + @Mock private RoleManager mRoleManager; private MediaProjectionInfo mMediaProjectionInfo; @Captor @@ -93,7 +98,8 @@ public class SensitiveContentProtectionManagerServiceContentTest { mSensitiveContentProtectionManagerService = new SensitiveContentProtectionManagerService(mContext); mSensitiveContentProtectionManagerService.init(mProjectionManager, mWindowManager, - mPackageManagerInternal, new ArraySet<>(Set.of(mExemptedScreenRecorderPackage))); + mPackageManagerInternal, mRoleManager, + new ArraySet<>(Set.of(mExemptedScreenRecorderPackage))); verify(mProjectionManager).addCallback(mMediaProjectionCallbackCaptor.capture(), any()); mMediaPorjectionCallback = mMediaProjectionCallbackCaptor.getValue(); mMediaProjectionInfo = @@ -152,7 +158,7 @@ public class SensitiveContentProtectionManagerServiceContentTest { String testAutofillService = mScreenRecorderPackage + "/com.example.SampleAutofillService"; int userId = Process.myUserHandle().getIdentifier(); Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.AUTOFILL_SERVICE, testAutofillService , userId); + Settings.Secure.AUTOFILL_SERVICE, testAutofillService, userId); mMediaPorjectionCallback.onStart(mMediaProjectionInfo); mSensitiveContentProtectionManagerService.setSensitiveContentProtection( @@ -169,6 +175,19 @@ public class SensitiveContentProtectionManagerServiceContentTest { verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } + @Test + public void testAppStreamingRoleHolderExemption() { + when(mRoleManager.getRoleHoldersAsUser( + AssociationRequest.DEVICE_PROFILE_APP_STREAMING, + mMediaProjectionInfo.getUserHandle())).thenReturn( + List.of(mMediaProjectionInfo.getPackageName())); + + mMediaPorjectionCallback.onStart(mMediaProjectionInfo); + mSensitiveContentProtectionManagerService.setSensitiveContentProtection( + mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); + } + private void mockDisabledViaDeveloperOption() { Settings.Global.putInt( mContext.getContentResolver(), diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java index 32135f1cb7fa..3c6e18f822af 100644 --- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java @@ -33,6 +33,8 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.role.RoleManager; +import android.companion.AssociationRequest; import android.content.pm.PackageManagerInternal; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; @@ -67,6 +69,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.List; import java.util.Set; @SmallTest @@ -116,6 +119,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { private PackageManagerInternal mPackageManagerInternal; @Mock + private RoleManager mRoleManager; + + @Mock private StatusBarNotification mNotification1; @Mock @@ -161,7 +167,8 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { setupSensitiveNotification(); mSensitiveContentProtectionManagerService.init(mProjectionManager, mWindowManager, - mPackageManagerInternal, new ArraySet<>(Set.of(EXEMPTED_SCREEN_RECORDER_PACKAGE))); + mPackageManagerInternal, mRoleManager, + new ArraySet<>(Set.of(EXEMPTED_SCREEN_RECORDER_PACKAGE))); // Obtain useful mMediaProjectionCallback verify(mProjectionManager).addCallback(mMediaProjectionCallbackCaptor.capture(), any()); @@ -315,6 +322,18 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { } @Test + public void mediaProjectionOnStart_verifyExemptedAppStreamingPackage() { + MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo(); + when(mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING, + mediaProjectionInfo.getUserHandle())).thenReturn( + List.of(mediaProjectionInfo.getPackageName())); + + mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo); + + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); + } + + @Test public void mediaProjectionOnStart_verifyExemptedRecorderPackage() { MediaProjectionInfo mediaProjectionInfo = createExemptMediaProjectionInfo(); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java index fa5847560782..4b53f1309337 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java @@ -23,18 +23,11 @@ import static com.android.server.am.ActivityManagerService.Injector; import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -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.Mockito.anyInt; import static org.mockito.Mockito.doReturn; -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.app.ActivityManagerInternal; import android.app.ActivityManagerInternal.FrozenProcessListener; import android.content.ComponentName; import android.content.Context; @@ -44,14 +37,12 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.MessageQueue; import android.os.Process; -import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.text.TextUtils; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.internal.annotations.GuardedBy; import com.android.modules.utils.testing.ExtendedMockitoRule; import com.android.modules.utils.testing.TestableDeviceConfig; import com.android.server.LocalServices; @@ -68,11 +59,9 @@ import org.mockito.Mock; import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** @@ -164,7 +153,7 @@ public final class CachedAppOptimizerTest { app.info.uid = packageUid; // Exact value does not mater, it can be any state for which compaction is allowed. app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE); - app.mState.setSetAdj(899); + app.mState.setSetAdj(940); app.mState.setCurAdj(940); return app; } 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 1ef758cf192e..340115a7d465 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -3337,6 +3337,108 @@ public class MockingOomAdjusterTests { followUpTimeCaptor.capture()); } + /** + * For Perceptible Tasks adjustment, this solely unit-tests OomAdjuster -> onOtherActivity() + */ + @SuppressWarnings("GuardedBy") + @Test + @EnableFlags(Flags.FLAG_PERCEPTIBLE_TASKS) + public void testPerceptibleAdjustment() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); + + long now = mInjector.getUptimeMillis(); + + // GIVEN: perceptible adjustment is NOT enabled (perceptible stop time is not set) + // EXPECT: zero adjustment + // TLDR: App is not set as a perceptible task and hence no oom_adj boosting. + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ, + false, false, PROCESS_STATE_CACHED_ACTIVITY, + SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND); + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(-1); + assertEquals(CACHED_APP_MIN_ADJ, mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj); + + // GIVEN: perceptible adjustment is enabled (perceptible stop time is set) and + // elapsed time < PERCEPTIBLE_TASK_TIMEOUT + // EXPECT: adjustment to PERCEPTIBLE_MEDIUM_APP_ADJ + // TLDR: App is a perceptible task (e.g. opened from launcher) and has oom_adj boosting. + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ, + false, false, PROCESS_STATE_CACHED_ACTIVITY, + SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND); + mInjector.reset(); + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(now); + assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ, + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj); + + // GIVEN: perceptible adjustment is enabled (perceptible stop time is set) and + // elapsed time > PERCEPTIBLE_TASK_TIMEOUT + // EXPECT: adjustment to PREVIOUS_APP_ADJ + // TLDR: App is a perceptible task (e.g. opened from launcher) and has oom_adj boosting, but + // time has elapsed and has dropped to a lower boosting of PREVIOUS_APP_ADJ + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ, + false, false, PROCESS_STATE_CACHED_ACTIVITY, + SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND); + mInjector.jumpUptimeAheadTo(OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS + 1000); + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(0); + assertEquals(PREVIOUS_APP_ADJ, mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj); + } + + /** + * For Perceptible Tasks adjustment, this tests overall adjustment flow. + */ + @SuppressWarnings("GuardedBy") + @Test + @EnableFlags(Flags.FLAG_PERCEPTIBLE_TASKS) + public void testUpdateOomAdjPerceptible() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); + WindowProcessController wpc = app.getWindowProcessController(); + + // Set uptime to be at least the timeout time + buffer, so that we don't end up with + // negative stopTime in our test input + mInjector.jumpUptimeAheadTo(OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS + 60L * 1000L); + long now = mInjector.getUptimeMillis(); + doReturn(true).when(wpc).hasActivities(); + + // GIVEN: perceptible adjustment is is enabled + // EXPECT: perceptible-act adjustment + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) + .when(wpc).getActivityStateFlags(); + doReturn(now).when(wpc).getPerceptibleTaskStoppedTimeMillis(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_IMPORTANT_BACKGROUND, PERCEPTIBLE_MEDIUM_APP_ADJ, + SCHED_GROUP_BACKGROUND, "perceptible-act"); + + // GIVEN: perceptible adjustment is is enabled and timeout has been reached + // EXPECT: stale-perceptible-act adjustment + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) + .when(wpc).getActivityStateFlags(); + + doReturn(now - OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS).when( + wpc).getPerceptibleTaskStoppedTimeMillis(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ, + SCHED_GROUP_BACKGROUND, "stale-perceptible-act"); + + // GIVEN: perceptible adjustment is is disabled + // EXPECT: no perceptible adjustment + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) + .when(wpc).getActivityStateFlags(); + doReturn(Long.MIN_VALUE).when(wpc).getPerceptibleTaskStoppedTimeMillis(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_CACHED_ACTIVITY, CACHED_APP_MIN_ADJ, + SCHED_GROUP_BACKGROUND, "cch-act"); + + // GIVEN: perceptible app is in foreground + // EXPECT: no perceptible adjustment + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE) + .when(wpc).getActivityStateFlags(); + doReturn(now).when(wpc).getPerceptibleTaskStoppedTimeMillis(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, + SCHED_GROUP_DEFAULT, "vis-activity"); + } + @SuppressWarnings("GuardedBy") @Test public void testUpdateOomAdj_DoAll_Multiple_Provider_Retention() { diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp index 8eae9c7d71fa..e030b3f19e4f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp +++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp @@ -32,18 +32,18 @@ android_test { static_libs: [ "androidx.test.core", "androidx.test.runner", + "flag-junit", "mockito-target-extended-minus-junit4", "services.core", "truth", - "flag-junit", ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), { "true": ["service-crashrecovery-pre-jarjar"], default: [], }), libs: [ - "android.test.mock.stubs.system", "android.test.base.stubs.system", + "android.test.mock.stubs.system", "android.test.runner.stubs.system", ], @@ -55,7 +55,9 @@ android_test { certificate: "platform", platform_apis: true, test_suites: [ - "device-tests", "automotive-tests", + "device-tests", + "mts-crashrecovery", ], + min_sdk_version: "36", } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java index 904545bd3cc3..b4e845171a0b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java @@ -276,6 +276,7 @@ public class JobServiceContextTest { final int jobId = 123; mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus); mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback); + mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING; doReturn(jobId).when(mMockJobStatus).getJobId(); mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId); @@ -296,7 +297,25 @@ public class JobServiceContextTest { mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback); mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId); + verify(mMockJobStatus, never()).setAbandoned(true); + + mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus); + doReturn(jobId).when(mMockJobStatus).getJobId(); + + mJobServiceContext.mVerb = JobServiceContext.VERB_BINDING; + mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId); + verify(mMockJobStatus, never()).setAbandoned(true); + mJobServiceContext.mVerb = JobServiceContext.VERB_STARTING; + mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId); + verify(mMockJobStatus, never()).setAbandoned(true); + + mJobServiceContext.mVerb = JobServiceContext.VERB_STOPPING; + mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId); + verify(mMockJobStatus, never()).setAbandoned(true); + + mJobServiceContext.mVerb = JobServiceContext.VERB_FINISHED; + mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId); verify(mMockJobStatus, never()).setAbandoned(true); } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java index 2cd105ba5317..67b26c1c0b00 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -60,6 +60,8 @@ import android.content.ComponentName; import android.content.pm.PackageManagerInternal; import android.net.Uri; import android.os.SystemClock; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.MediaStore; import android.util.SparseIntArray; @@ -71,6 +73,7 @@ import com.android.server.job.JobSchedulerService; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -92,6 +95,9 @@ public class JobStatusTest { private static final Uri IMAGES_MEDIA_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; private static final Uri VIDEO_MEDIA_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private JobSchedulerInternal mJobSchedulerInternal; private MockitoSession mMockingSession; @@ -1373,6 +1379,86 @@ public class JobStatusTest { assertEquals("@TestNamespace@TestTag:foo", jobStatus.getBatteryName()); } + @Test + @EnableFlags({ + com.android.server.job.Flags.FLAG_INCLUDE_TRACE_TAG_IN_JOB_NAME, + android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS + }) + public void testJobName_NotTagNoNamespace_IncludeTraceTagInJobNameEnabled() { + JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setTraceTag("TestTraceTag") + .build(); + JobStatus jobStatus = createJobStatus(jobInfo, null, -1, null, null); + assertEquals("#TestTraceTag#foo/bar", jobStatus.getBatteryName()); + } + + @Test + @EnableFlags({ + com.android.server.job.Flags.FLAG_INCLUDE_TRACE_TAG_IN_JOB_NAME, + android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS + }) + public void testJobName_NoTagWithNamespace_IncludeTraceTagInJobNameEnabled() { + JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setTraceTag("TestTraceTag") + .build(); + JobStatus jobStatus = createJobStatus(jobInfo, null, -1, "TestNamespace", null); + assertEquals("#TestTraceTag#@TestNamespace@foo/bar", jobStatus.getBatteryName()); + } + + @Test + @EnableFlags({ + com.android.server.job.Flags.FLAG_INCLUDE_TRACE_TAG_IN_JOB_NAME, + android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS + }) + public void testJobName_WithTagNoNamespace_IncludeTraceTagInJobNameEnabled() { + JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setTraceTag("TestTraceTag") + .build(); + JobStatus jobStatus = createJobStatus(jobInfo, SOURCE_PACKAGE, 0, null, "TestTag"); + assertEquals("#TestTraceTag#TestTag:foo", jobStatus.getBatteryName()); + } + + @Test + @EnableFlags({ + com.android.server.job.Flags.FLAG_INCLUDE_TRACE_TAG_IN_JOB_NAME, + android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS + }) + public void testJobName_FilteredTraceTagEmail_IncludeTraceTagInJobNameEnabled() { + JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setTraceTag("test@email.com") + .build(); + JobStatus jobStatus = createJobStatus(jobInfo, SOURCE_PACKAGE, 0, null, "TestTag"); + assertEquals("#[EMAIL]#TestTag:foo", jobStatus.getBatteryName()); + } + + @Test + @EnableFlags({ + com.android.server.job.Flags.FLAG_INCLUDE_TRACE_TAG_IN_JOB_NAME, + android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS + }) + public void testJobName_FilteredTraceTagPhone_IncludeTraceTagInJobNameEnabled() { + JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setTraceTag("123-456-7890") + .build(); + JobStatus jobStatus = createJobStatus(jobInfo, SOURCE_PACKAGE, 0, null, "TestTag"); + assertEquals("#[PHONE]#TestTag:foo", jobStatus.getBatteryName()); + } + + @Test + @EnableFlags({ + com.android.server.job.Flags.FLAG_INCLUDE_TRACE_TAG_IN_JOB_NAME, + android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS + }) + public void testJobName_WithTagAndNamespace_IncludeTraceTagInJobNameEnabled() { + JobInfo jobInfo = + new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setTraceTag("TestTraceTag") + .build(); + JobStatus jobStatus = + createJobStatus(jobInfo, SOURCE_PACKAGE, 0, "TestNamespace", "TestTag"); + assertEquals("#TestTraceTag#@TestNamespace@TestTag:foo", jobStatus.getBatteryName()); + } + private void markExpeditedQuotaApproved(JobStatus job, boolean isApproved) { if (job.isRequestedExpeditedJob()) { job.setExpeditedJobQuotaApproved(sElapsedRealtimeClock.millis(), isApproved); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index c6870adb8464..4e4b3df3c935 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -295,15 +295,15 @@ public class QuotaControllerTest { } private void setCharging() { - doReturn(true).when(mJobSchedulerService).isBatteryCharging(); synchronized (mQuotaController.mLock) { + doReturn(true).when(mJobSchedulerService).isBatteryCharging(); mQuotaController.onBatteryStateChangedLocked(); } } private void setDischarging() { - doReturn(false).when(mJobSchedulerService).isBatteryCharging(); synchronized (mQuotaController.mLock) { + doReturn(false).when(mJobSchedulerService).isBatteryCharging(); mQuotaController.onBatteryStateChangedLocked(); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp index 5a802d9de2ff..36b064b9b090 100644 --- a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp @@ -30,18 +30,18 @@ android_test { static_libs: [ "androidx.test.runner", + "flag-junit", "mockito-target-extended-minus-junit4", "services.core", "truth", - "flag-junit", ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), { "true": ["service-crashrecovery-pre-jarjar"], default: [], }), libs: [ - "android.test.mock.stubs.system", "android.test.base.stubs.system", + "android.test.mock.stubs.system", "android.test.runner.stubs.system", ], @@ -53,9 +53,11 @@ android_test { certificate: "platform", platform_apis: true, test_suites: [ - "device-tests", "automotive-tests", + "device-tests", + "mts-crashrecovery", ], + min_sdk_version: "36", } test_module_config { diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java index 347dc81c6734..fb4d81ac831c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java @@ -43,7 +43,6 @@ import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; -import android.crashrecovery.flags.Flags; import android.os.Handler; import android.os.MessageQueue; import android.os.SystemProperties; @@ -273,7 +272,6 @@ public class RollbackPackageHealthObserverTest { @Test public void healthCheckFailed_impactLevelLow_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -304,7 +302,6 @@ public class RollbackPackageHealthObserverTest { @Test public void healthCheckFailed_impactLevelHigh_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -335,7 +332,6 @@ public class RollbackPackageHealthObserverTest { @Test public void healthCheckFailed_impactLevelManualOnly_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -365,7 +361,6 @@ public class RollbackPackageHealthObserverTest { @Test public void healthCheckFailed_impactLevelLowAndHigh_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -404,7 +399,6 @@ public class RollbackPackageHealthObserverTest { @Test public void execute_impactLevelLow_nativeCrash_rollback() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -438,7 +432,6 @@ public class RollbackPackageHealthObserverTest { @Test public void execute_impactLevelLow_rollbackFailedPackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -483,7 +476,6 @@ public class RollbackPackageHealthObserverTest { @Test public void execute_impactLevelLow_rollbackAll() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -530,7 +522,6 @@ public class RollbackPackageHealthObserverTest { @Test public void execute_impactLevelLowAndHigh_rollbackLow() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -578,7 +569,6 @@ public class RollbackPackageHealthObserverTest { @Test public void execute_impactLevelHigh_rollbackHigh() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId2 = 2; VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); @@ -612,7 +602,6 @@ public class RollbackPackageHealthObserverTest { */ @Test public void onBootLoop_impactLevelLow_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -637,7 +626,6 @@ public class RollbackPackageHealthObserverTest { @Test public void onBootLoop_impactLevelHigh_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -662,7 +650,6 @@ public class RollbackPackageHealthObserverTest { @Test public void onBootLoop_impactLevelHighDisableHighImpactRollback_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(true)); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -692,7 +679,6 @@ public class RollbackPackageHealthObserverTest { @Test public void onBootLoop_impactLevelManualOnly_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -720,7 +706,6 @@ public class RollbackPackageHealthObserverTest { @Test public void onBootLoop_impactLevelLowAndHigh_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -757,7 +742,6 @@ public class RollbackPackageHealthObserverTest { @Test public void executeBootLoopMitigation_impactLevelLow_rollbackAll() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -802,7 +786,6 @@ public class RollbackPackageHealthObserverTest { @Test public void executeBootLoopMitigation_impactLevelLowAndHigh_rollbackLow() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -847,7 +830,6 @@ public class RollbackPackageHealthObserverTest { @Test public void executeBootLoopMitigation_impactLevelHigh_rollbackHigh() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId2 = 2; VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); @@ -882,7 +864,6 @@ public class RollbackPackageHealthObserverTest { @Test public void execute_impactLevelLowAndManual_rollbackLowImpactOnly() throws PackageManager.NameNotFoundException, InterruptedException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -928,7 +909,6 @@ public class RollbackPackageHealthObserverTest { @Test public void execute_impactLevelManual_rollbackLowImpactOnly() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -962,7 +942,6 @@ public class RollbackPackageHealthObserverTest { @Test public void executeBootLoopMitigation_impactLevelHighMultiplePackage_rollbackHigh() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); @@ -1008,7 +987,6 @@ public class RollbackPackageHealthObserverTest { @Test public void executeBootLoopMitigation_impactLevelHighKillSwitchTrue_rollbackHigh() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(true)); int rollbackId1 = 1; VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java index 83a390d7f70b..4e56422ec391 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java @@ -437,6 +437,42 @@ public class NotifierTest { } @Test + public void testOnGroupChanged_perDisplayWakeByTouchEnabled() { + createNotifier(); + // GIVEN per-display wake by touch is enabled and one display group has been defined with + // two displays + when(mPowerManagerFlags.isPerDisplayWakeByTouchEnabled()).thenReturn(true); + final int groupId = 121; + final int displayId1 = 1221; + final int displayId2 = 1222; + final int[] displays = new int[]{displayId1, displayId2}; + when(mDisplayManagerInternal.getDisplayIds()).thenReturn(IntArray.wrap(displays)); + when(mDisplayManagerInternal.getDisplayIdsForGroup(groupId)).thenReturn(displays); + SparseArray<int[]> displayIdsByGroupId = new SparseArray<>(); + displayIdsByGroupId.put(groupId, displays); + when(mDisplayManagerInternal.getDisplayIdsByGroupsIds()).thenReturn(displayIdsByGroupId); + mNotifier.onGroupWakefulnessChangeStarted( + groupId, WAKEFULNESS_AWAKE, PowerManager.WAKE_REASON_TAP, /* eventTime= */ 1000); + final SparseBooleanArray expectedDisplayInteractivities = new SparseBooleanArray(); + expectedDisplayInteractivities.put(displayId1, true); + expectedDisplayInteractivities.put(displayId2, true); + verify(mInputManagerInternal).setDisplayInteractivities(expectedDisplayInteractivities); + + // WHEN display group is changed to only contain one display + SparseArray<int[]> newDisplayIdsByGroupId = new SparseArray<>(); + newDisplayIdsByGroupId.put(groupId, new int[]{displayId1}); + when(mDisplayManagerInternal.getDisplayIdsByGroupsIds()).thenReturn(newDisplayIdsByGroupId); + mNotifier.onGroupChanged(); + + // THEN native input manager is informed that the displays in the group have changed + final SparseBooleanArray expectedDisplayInteractivitiesAfterChange = + new SparseBooleanArray(); + expectedDisplayInteractivitiesAfterChange.put(displayId1, true); + verify(mInputManagerInternal).setDisplayInteractivities( + expectedDisplayInteractivitiesAfterChange); + } + + @Test public void testOnWakeLockReleased_FrameworkStatsLogged_NoChains() { when(mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled()).thenReturn(true); createNotifier(); diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java index 29a17e1c85ab..ff6796561926 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java @@ -2501,6 +2501,49 @@ public class PowerManagerServiceTest { } @Test + public void testMultiDisplay_twoDisplays_onlyDefaultDisplayCanDream() { + final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; + final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1; + final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener = + new AtomicReference<>(); + doAnswer((Answer<Void>) invocation -> { + listener.set(invocation.getArgument(0)); + return null; + }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any()); + final DisplayInfo info = new DisplayInfo(); + info.displayGroupId = nonDefaultDisplayGroupId; + when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info); + when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true); + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1); + doAnswer(inv -> { + when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); + return null; + }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString()); + + setMinimumScreenOffTimeoutConfig(5); + createService(); + startSystem(); + + listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId); + + assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo( + WAKEFULNESS_AWAKE); + assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo( + WAKEFULNESS_AWAKE); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + + advanceTime(15000); + + // Only the default display group is dreaming. + assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo( + WAKEFULNESS_DREAMING); + assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo( + WAKEFULNESS_DOZING); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); + } + + @Test public void testMultiDisplay_addNewDisplay_becomeGloballyAwakeButDefaultRemainsDozing() { final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1; diff --git a/services/tests/powerstatstests/res/raw/battery-history.zip b/services/tests/powerstatstests/res/raw/battery-history.zip Binary files differnew file mode 100644 index 000000000000..ed82ac0f79cc --- /dev/null +++ b/services/tests/powerstatstests/res/raw/battery-history.zip diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java new file mode 100644 index 000000000000..6be9c6d4b80c --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2025 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.power.stats; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.content.res.Resources; +import android.os.BatteryConsumer; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.os.ConditionVariable; +import android.os.FileUtils; +import android.os.Handler; +import android.os.HandlerThread; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; + +import com.android.internal.os.Clock; +import com.android.internal.os.CpuScalingPolicies; +import com.android.internal.os.CpuScalingPolicyReader; +import com.android.internal.os.MonotonicClock; +import com.android.internal.os.PowerProfile; +import com.android.server.power.stats.processor.MultiStatePowerAttributor; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +@RunWith(AndroidJUnit4.class) +@LargeTest +@android.platform.test.annotations.DisabledOnRavenwood(reason = "Performance test") +@Ignore("Performance experiment. Comment out @Ignore to run") +public class BatteryUsageStatsProviderPerfTest { + @Rule + public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + private final Clock mClock = new MockClock(); + private MonotonicClock mMonotonicClock; + private PowerProfile mPowerProfile; + private CpuScalingPolicies mCpuScalingPolicies; + private File mDirectory; + private Handler mHandler; + private MockBatteryStatsImpl mBatteryStats; + + @Before + public void setup() throws Exception { + Context context = InstrumentationRegistry.getContext(); + mPowerProfile = new PowerProfile(context); + mCpuScalingPolicies = new CpuScalingPolicyReader().read(); + + HandlerThread mHandlerThread = new HandlerThread("batterystats-handler"); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + + // Extract accumulated battery history to ensure consistent iterations + mDirectory = Files.createTempDirectory("BatteryUsageStatsProviderPerfTest").toFile(); + File historyDirectory = new File(mDirectory, "battery-history"); + historyDirectory.mkdir(); + + long maxMonotonicTime = 0; + + // To recreate battery-history.zip if necessary, perform these commands: + // cd /tmp + // mkdir battery-history + // adb pull /data/system/battery-history + // zip battery-history.zip battery-history/* + // cp battery-history.zip \ + // $ANDROID_BUILD_TOP/frameworks/base/services/tests/powerstatstests/res/raw + Resources resources = context.getResources(); + int resId = resources.getIdentifier("battery-history", "raw", context.getPackageName()); + try (InputStream in = resources.openRawResource(resId)) { + try (ZipInputStream zis = new ZipInputStream(in)) { + ZipEntry ze; + while ((ze = zis.getNextEntry()) != null) { + if (!ze.getName().endsWith(".bh")) { + continue; + } + File file = new File(mDirectory, ze.getName()); + try (OutputStream out = new FileOutputStream( + file)) { + FileUtils.copy(zis, out); + } + long timestamp = Long.parseLong(file.getName().replace(".bh", "")); + if (timestamp > maxMonotonicTime) { + maxMonotonicTime = timestamp; + } + } + } + } + + mMonotonicClock = new MonotonicClock(maxMonotonicTime + 1000000000, mClock); + mBatteryStats = new MockBatteryStatsImpl(mClock, mDirectory); + } + + @Test + public void getBatteryUsageStats_accumulated() throws IOException { + BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() + .setMaxStatsAgeMs(0) + .includePowerStateData() + .includeScreenStateData() + .includeProcessStateData() + .accumulated() + .build(); + + double expectedCpuPower = 0; + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + + waitForBackgroundThread(); + + BatteryUsageStatsProvider provider = createBatteryUsageStatsProvider(); + state.resumeTiming(); + + BatteryUsageStats stats = provider.getBatteryUsageStats(mBatteryStats, query); + waitForBackgroundThread(); + + state.pauseTiming(); + + double cpuConsumedPower = stats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU); + assertThat(cpuConsumedPower).isNonZero(); + if (expectedCpuPower == 0) { + expectedCpuPower = cpuConsumedPower; + } else { + // Verify that all iterations produce the same result + assertThat(cpuConsumedPower).isEqualTo(expectedCpuPower); + } + stats.close(); + + state.resumeTiming(); + } + } + + private BatteryUsageStatsProvider createBatteryUsageStatsProvider() { + Context context = InstrumentationRegistry.getContext(); + + PowerStatsStore store = new PowerStatsStore(mDirectory, mHandler); + store.reset(); + + MultiStatePowerAttributor powerAttributor = new MultiStatePowerAttributor(context, store, + mPowerProfile, mCpuScalingPolicies, mPowerProfile::getBatteryCapacity); + return new BatteryUsageStatsProvider(context, powerAttributor, mPowerProfile, + mCpuScalingPolicies, store, 10000000, mClock, mMonotonicClock); + } + + private void waitForBackgroundThread() { + ConditionVariable done = new ConditionVariable(); + mHandler.post(done::open); + done.block(); + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java index cb9d9b12a2fc..dcddf06f01fb 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java @@ -34,6 +34,7 @@ import static org.junit.Assert.fail; import android.os.BatteryConsumer; import android.os.PersistableBundle; import android.platform.test.ravenwood.RavenwoodRule; +import android.util.IntArray; import android.util.LongArray; import androidx.test.filters.SmallTest; @@ -51,7 +52,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -315,8 +315,12 @@ public class CpuPowerStatsProcessorTest { } @Override - void collectUids(Collection<Integer> uids) { - uids.addAll(mUids); + IntArray getActiveUids() { + IntArray uids = new IntArray(); + for (Integer uid : mUids) { + uids.add(uid); + } + return uids; } void verifyPowerEstimates() { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java index a232c0c7aec9..3b614bdb38ed 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java @@ -143,6 +143,8 @@ public class MultiStateStatsTest { multiStateStats.increment(new long[]{200, 200}, 5000); + multiStateStats.increment(null, 6000); // No-op + long[] stats = new long[DIMENSION_COUNT]; multiStateStats.getStats(stats, new int[]{0, BatteryConsumer.PROCESS_STATE_FOREGROUND, 0}); // (400 - 100) * 0.5 + (600 - 400) * 0.5 diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java index 298d27e2e8c4..879aa4893802 100644 --- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java +++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java @@ -17,7 +17,6 @@ package com.android.server.security.intrusiondetection; import static android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE; -import static android.Manifest.permission.INTERNET; import static android.Manifest.permission.MANAGE_INTRUSION_DETECTION_STATE; import static android.Manifest.permission.READ_INTRUSION_DETECTION_STATE; @@ -28,7 +27,6 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; @@ -37,8 +35,8 @@ import static org.mockito.Mockito.verify; import android.annotation.SuppressLint; import android.app.admin.ConnectEvent; +import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DnsEvent; -import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; import android.content.ComponentName; import android.content.Context; @@ -53,37 +51,22 @@ import android.os.test.TestLooper; import android.security.intrusiondetection.IIntrusionDetectionServiceCommandCallback; import android.security.intrusiondetection.IIntrusionDetectionServiceStateCallback; import android.security.intrusiondetection.IntrusionDetectionEvent; -import android.security.keystore.KeyGenParameterSpec; -import android.security.keystore.KeyProperties; import android.util.Log; import androidx.test.core.app.ApplicationProvider; import com.android.bedstead.harrier.BedsteadJUnit4; import com.android.bedstead.multiuser.annotations.RequireRunOnSystemUser; -import com.android.bedstead.nene.TestApis; -import com.android.bedstead.nene.devicepolicy.DeviceOwner; import com.android.bedstead.permissions.CommonPermissions; -import com.android.bedstead.permissions.PermissionContext; import com.android.bedstead.permissions.annotations.EnsureHasPermission; import com.android.coretests.apps.testapp.LocalIntrusionDetectionEventTransport; import com.android.server.ServiceThread; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.KeyStore; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -107,7 +90,6 @@ public class IntrusionDetectionServiceTest { private static final int ERROR_DATA_SOURCE_UNAVAILABLE = IIntrusionDetectionServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE; - private static DeviceOwner sDeviceOwner; private Context mContext; private IntrusionDetectionEventTransportConnection mIntrusionDetectionEventTransportConnection; @@ -124,6 +106,8 @@ public class IntrusionDetectionServiceTest { "com.android.coretests.apps.testapp"; private static final String TEST_SERVICE = TEST_PKG + ".TestLoggingService"; + DevicePolicyManagerInternal mDevicePolicyManagerInternal; + @SuppressLint("VisibleForTests") @Before public void setUp() throws Exception { @@ -189,6 +173,7 @@ public class IntrusionDetectionServiceTest { } @Test + @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void testAddStateCallback_Disabled_TwoStateCallbacks() throws RemoteException { StateCallback scb1 = new StateCallback(); assertEquals(STATE_UNKNOWN, scb1.mState); @@ -204,7 +189,7 @@ public class IntrusionDetectionServiceTest { } @Test - @Ignore("Unit test does not run as system service UID") + @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void testRemoveStateCallback() throws RemoteException { mIntrusionDetectionService.setState(STATE_DISABLED); StateCallback scb1 = new StateCallback(); @@ -220,15 +205,19 @@ public class IntrusionDetectionServiceTest { mIntrusionDetectionService.getBinderService().removeStateCallback(scb2); CommandCallback ccb = new CommandCallback(); + + // Enable will fail; caller does not run as system server. + doNothing().when(mDataAggregator).enable(); mIntrusionDetectionService.getBinderService().enable(ccb); + mTestLooper.dispatchAll(); assertEquals(STATE_ENABLED, scb1.mState); assertEquals(STATE_DISABLED, scb2.mState); assertNull(ccb.mErrorCode); } - @Ignore("Unit test does not run as system service UID") @Test + @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void testEnable_FromDisabled_TwoStateCallbacks() throws RemoteException { mIntrusionDetectionService.setState(STATE_DISABLED); StateCallback scb1 = new StateCallback(); @@ -243,6 +232,9 @@ public class IntrusionDetectionServiceTest { CommandCallback ccb = new CommandCallback(); mIntrusionDetectionService.getBinderService().enable(ccb); + + // Enable will fail; caller does not run as system server. + doNothing().when(mDataAggregator).enable(); mTestLooper.dispatchAll(); verify(mDataAggregator, times(1)).enable(); @@ -319,7 +311,7 @@ public class IntrusionDetectionServiceTest { assertNull(ccb.mErrorCode); } - @Ignore("Enable once the IntrusionDetectionEventTransportConnection is ready") + @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) @Test public void testEnable_FromDisable_TwoStateCallbacks_TransportUnavailable() throws RemoteException { @@ -390,146 +382,6 @@ public class IntrusionDetectionServiceTest { } @Test - @Ignore("Unit test does not run as system service UID") - @RequireRunOnSystemUser - @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) - public void testDataAggregator_AddSecurityEvent() throws Exception { - mIntrusionDetectionService.setState(STATE_ENABLED); - ServiceThread mockThread = spy(ServiceThread.class); - mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread); - - // SecurityLogging generates a number of events and callbacks, so create a latch to wait for - // the given event. - String eventString = this.getClass().getName() + ".testSecurityEvent"; - - final CountDownLatch latch = new CountDownLatch(1); - // TODO: Replace this mock when the IntrusionDetectionEventTransportConnection is ready. - doAnswer( - new Answer<Boolean>() { - @Override - public Boolean answer(InvocationOnMock input) { - List<IntrusionDetectionEvent> receivedEvents = - (List<IntrusionDetectionEvent>) input.getArguments()[0]; - for (IntrusionDetectionEvent event : receivedEvents) { - if (event.getType() == IntrusionDetectionEvent.SECURITY_EVENT) { - SecurityEvent securityEvent = event.getSecurityEvent(); - Object[] eventData = (Object[]) securityEvent.getData(); - if (securityEvent.getTag() == SecurityLog.TAG_KEY_GENERATED - && eventData[1].equals(eventString)) { - latch.countDown(); - } - } - } - return true; - } - }) - .when(mIntrusionDetectionEventTransportConnection).addData(any()); - mDataAggregator.enable(); - - // Generate the security event. - generateSecurityEvent(eventString); - TestApis.devicePolicy().forceSecurityLogs(); - - // Verify the event is received. - mTestLooper.startAutoDispatch(); - assertTrue(latch.await(1, TimeUnit.SECONDS)); - mTestLooper.stopAutoDispatch(); - - mDataAggregator.disable(); - } - - @Test - @RequireRunOnSystemUser - @Ignore("Unit test does not run as system service UID") - @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) - public void testDataAggregator_AddNetworkEvent() throws Exception { - mIntrusionDetectionService.setState(STATE_ENABLED); - ServiceThread mockThread = spy(ServiceThread.class); - mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread); - - // Network logging may log multiple and callbacks, so create a latch to wait for - // the given event. - // eventServer must be a valid domain to generate a network log event. - String eventServer = "google.com"; - final CountDownLatch latch = new CountDownLatch(1); - // TODO: Replace this mock when the IntrusionDetectionEventTransportConnection is ready. - doAnswer( - new Answer<Boolean>() { - @Override - public Boolean answer(InvocationOnMock input) { - List<IntrusionDetectionEvent> receivedEvents = - (List<IntrusionDetectionEvent>) input.getArguments()[0]; - for (IntrusionDetectionEvent event : receivedEvents) { - if (event.getType() - == IntrusionDetectionEvent.NETWORK_EVENT_DNS) { - DnsEvent dnsEvent = event.getDnsEvent(); - if (dnsEvent.getHostname().equals(eventServer)) { - latch.countDown(); - } - } - } - return true; - } - }) - .when(mIntrusionDetectionEventTransportConnection).addData(any()); - mDataAggregator.enable(); - - // Generate the network event. - generateNetworkEvent(eventServer); - TestApis.devicePolicy().forceNetworkLogs(); - - // Verify the event is received. - mTestLooper.startAutoDispatch(); - assertTrue(latch.await(1, TimeUnit.SECONDS)); - mTestLooper.stopAutoDispatch(); - - mDataAggregator.disable(); - } - - /** Emits a given string into security log (if enabled). */ - private void generateSecurityEvent(String eventString) - throws IllegalArgumentException, GeneralSecurityException, IOException { - if (eventString == null || eventString.isEmpty()) { - throw new IllegalArgumentException( - "Error generating security event: eventString must not be empty"); - } - - final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore"); - keyGen.initialize( - new KeyGenParameterSpec.Builder(eventString, KeyProperties.PURPOSE_SIGN).build()); - // Emit key generation event. - final KeyPair keyPair = keyGen.generateKeyPair(); - assertNotNull(keyPair); - - final KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); - ks.load(null); - // Emit key destruction event. - ks.deleteEntry(eventString); - } - - /** Emits a given string into network log (if enabled). */ - private void generateNetworkEvent(String server) throws IllegalArgumentException, IOException { - if (server == null || server.isEmpty()) { - throw new IllegalArgumentException( - "Error generating network event: server must not be empty"); - } - - HttpURLConnection urlConnection = null; - int connectionTimeoutMS = 2_000; - try (PermissionContext p = TestApis.permissions().withPermission(INTERNET)) { - final URL url = new URL("http://" + server); - urlConnection = (HttpURLConnection) url.openConnection(); - urlConnection.setConnectTimeout(connectionTimeoutMS); - urlConnection.setReadTimeout(connectionTimeoutMS); - urlConnection.getResponseCode(); - } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } - } - } - - @Test @RequireRunOnSystemUser @EnsureHasPermission( android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE) diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 834fea46e505..4531b3948495 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -199,6 +199,10 @@ <service android:name="com.android.server.job.MockBiasJobService" android:permission="android.permission.BIND_JOB_SERVICE"/> + <activity + android:name="android.app.Activity" + android:exported="false" /> + <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity"/> <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity2"/> <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity3"/> 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 8dfd54fe38bc..42b84bdc51e6 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -46,12 +46,11 @@ import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityActi import static android.view.accessibility.AccessibilityNodeInfo.FOCUS_INPUT; import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID; -import static org.hamcrest.Matchers.hasItems; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -65,6 +64,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityService; @@ -90,14 +90,20 @@ import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.test.FakePermissionEnforcer; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.Pair; import android.view.Display; import android.view.KeyEvent; import android.view.MagnificationSpec; +import android.view.SurfaceControl; +import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; +import android.view.accessibility.IWindowSurfaceInfoCallback; +import android.window.ScreenCapture; import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection; import com.android.server.accessibility.magnification.MagnificationProcessor; @@ -105,6 +111,7 @@ import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.WindowManagerInternal; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -121,6 +128,10 @@ import java.util.concurrent.Callable; * Tests for the AbstractAccessibilityServiceConnection */ public class AbstractAccessibilityServiceConnectionTest { + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final ComponentName COMPONENT_NAME = new ComponentName( "com.android.server.accessibility", ".AbstractAccessibilityServiceConnectionTest"); private static final String PACKAGE_NAME1 = "com.android.server.accessibility1"; @@ -264,7 +275,7 @@ public class AbstractAccessibilityServiceConnectionTest { @Test public void getCapabilities() { - assertThat(mServiceConnection.getCapabilities(), is(A11Y_SERVICE_CAPABILITY)); + assertThat(mServiceConnection.getCapabilities()).isEqualTo(A11Y_SERVICE_CAPABILITY); } @Test @@ -329,7 +340,7 @@ public class AbstractAccessibilityServiceConnectionTest { 0, null, 0); mServiceConnection.setServiceInfo(serviceInfo); - assertThat(mServiceConnection.canReceiveEventsLocked(), is(true)); + assertThat(mServiceConnection.canReceiveEventsLocked()).isTrue(); } @Test @@ -348,9 +359,11 @@ public class AbstractAccessibilityServiceConnectionTest { mServiceConnection.getWindows(); assertEquals(2, allWindows.size()); - assertThat(allWindows.get(Display.DEFAULT_DISPLAY), is(mA11yWindowInfos)); + assertThat(allWindows.get(Display.DEFAULT_DISPLAY)) + .containsExactlyElementsIn(mA11yWindowInfos); assertEquals(2, allWindows.get(Display.DEFAULT_DISPLAY).size()); - assertThat(allWindows.get(SECONDARY_DISPLAY_ID), is(mA11yWindowInfosOnSecondDisplay)); + assertThat(allWindows.get(SECONDARY_DISPLAY_ID)) + .containsExactlyElementsIn(mA11yWindowInfosOnSecondDisplay); assertEquals(1, allWindows.get(SECONDARY_DISPLAY_ID).size()); } @@ -358,12 +371,12 @@ public class AbstractAccessibilityServiceConnectionTest { public void getWindows_returnNull() { // no canRetrieveWindows, should return null when(mMockSecurityPolicy.canRetrieveWindowsLocked(mServiceConnection)).thenReturn(false); - assertThat(mServiceConnection.getWindows(), is(nullValue())); + assertThat(mServiceConnection.getWindows()).isNull(); // no checkAccessibilityAccess, should return null when(mMockSecurityPolicy.canRetrieveWindowsLocked(mServiceConnection)).thenReturn(true); when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false); - assertThat(mServiceConnection.getWindows(), is(nullValue())); + assertThat(mServiceConnection.getWindows()).isNull(); } @Test @@ -378,19 +391,19 @@ public class AbstractAccessibilityServiceConnectionTest { @Test public void getWindow() { - assertThat(mServiceConnection.getWindow(WINDOWID), is(mA11yWindowInfos.get(0))); + assertThat(mServiceConnection.getWindow(WINDOWID)).isEqualTo(mA11yWindowInfos.get(0)); } @Test public void getWindow_returnNull() { // no canRetrieveWindows, should return null when(mMockSecurityPolicy.canRetrieveWindowsLocked(mServiceConnection)).thenReturn(false); - assertThat(mServiceConnection.getWindow(WINDOWID), is(nullValue())); + assertThat(mServiceConnection.getWindow(WINDOWID)).isNull(); // no checkAccessibilityAccess, should return null when(mMockSecurityPolicy.canRetrieveWindowsLocked(mServiceConnection)).thenReturn(true); when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false); - assertThat(mServiceConnection.getWindow(WINDOWID), is(nullValue())); + assertThat(mServiceConnection.getWindow(WINDOWID)).isNull(); } @Test @@ -405,8 +418,8 @@ public class AbstractAccessibilityServiceConnectionTest { @Test public void getWindow_onNonDefaultDisplay() { - assertThat(mServiceConnection.getWindow(WINDOWID_ONSECONDDISPLAY), - is(mA11yWindowInfosOnSecondDisplay.get(0))); + assertThat(mServiceConnection.getWindow(WINDOWID_ONSECONDDISPLAY)) + .isEqualTo(mA11yWindowInfosOnSecondDisplay.get(0)); } @Test @@ -415,9 +428,9 @@ public class AbstractAccessibilityServiceConnectionTest { when(mMockSecurityPolicy.canGetAccessibilityNodeInfoLocked( USER_ID, mServiceConnection, WINDOWID)).thenReturn(false); for (int i = 0; i < mFindA11yNodesFunctions.length; i++) { - assertThat(mFindA11yNodesFunctions[i].call(), is(nullValue())); + assertThat(mFindA11yNodesFunctions[i].call()).isNull(); } - assertThat(mPerformA11yAction.call(), is(false)); + assertThat(mPerformA11yAction.call()).isFalse(); verifyNoMoreInteractions(mMockIA11yInteractionConnection); verify(mMockSecurityPolicy, never()).computeValidReportedPackages(any(), anyInt()); @@ -428,9 +441,9 @@ public class AbstractAccessibilityServiceConnectionTest { throws Exception { when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false); for (int i = 0; i < mFindA11yNodesFunctions.length; i++) { - assertThat(mFindA11yNodesFunctions[i].call(), is(nullValue())); + assertThat(mFindA11yNodesFunctions[i].call()).isNull(); } - assertThat(mPerformA11yAction.call(), is(false)); + assertThat(mPerformA11yAction.call()).isFalse(); verifyNoMoreInteractions(mMockIA11yInteractionConnection); verify(mMockSecurityPolicy, never()).computeValidReportedPackages(any(), anyInt()); @@ -441,9 +454,9 @@ public class AbstractAccessibilityServiceConnectionTest { throws Exception { when(mMockA11yWindowManager.getConnectionLocked(USER_ID, WINDOWID)).thenReturn(null); for (int i = 0; i < mFindA11yNodesFunctions.length; i++) { - assertThat(mFindA11yNodesFunctions[i].call(), is(nullValue())); + assertThat(mFindA11yNodesFunctions[i].call()).isNull(); } - assertThat(mPerformA11yAction.call(), is(false)); + assertThat(mPerformA11yAction.call()).isFalse(); verifyNoMoreInteractions(mMockIA11yInteractionConnection); verify(mMockSecurityPolicy, never()).computeValidReportedPackages(any(), anyInt()); @@ -562,7 +575,7 @@ public class AbstractAccessibilityServiceConnectionTest { when(mMockFingerprintGestureDispatcher.isFingerprintGestureDetectionAvailable()) .thenReturn(true); final boolean result = mServiceConnection.isFingerprintGestureDetectionAvailable(); - assertThat(result, is(true)); + assertThat(result).isTrue(); } @Test @@ -573,14 +586,14 @@ public class AbstractAccessibilityServiceConnectionTest { // Return false if device does not support fingerprint when(mMockPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(false); boolean result = mServiceConnection.isFingerprintGestureDetectionAvailable(); - assertThat(result, is(false)); + assertThat(result).isFalse(); // Return false if service does not have flag when(mMockPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(true); mSpyServiceInfo.flags = A11Y_SERVICE_FLAG & ~FLAG_REQUEST_FINGERPRINT_GESTURES; mServiceConnection.setServiceInfo(mSpyServiceInfo); result = mServiceConnection.isFingerprintGestureDetectionAvailable(); - assertThat(result, is(false)); + assertThat(result).isFalse(); } @Test @@ -590,7 +603,7 @@ public class AbstractAccessibilityServiceConnectionTest { when(mMockMagnificationProcessor.getScale(displayId)).thenReturn(scale); final float result = mServiceConnection.getMagnificationScale(displayId); - assertThat(result, is(scale)); + assertThat(result).isEqualTo(scale); } @Test @@ -601,7 +614,7 @@ public class AbstractAccessibilityServiceConnectionTest { when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2); final float result = mServiceConnection.getMagnificationScale(displayId); - assertThat(result, is(1.0f)); + assertThat(result).isEqualTo(1.0f); } @Test @@ -616,7 +629,7 @@ public class AbstractAccessibilityServiceConnectionTest { when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2); final Region result = mServiceConnection.getMagnificationRegion(displayId); - assertThat(result.isEmpty(), is(true)); + assertWithMessage("Non-empty region: " + result).that(result.isEmpty()).isTrue(); } @Test @@ -642,7 +655,7 @@ public class AbstractAccessibilityServiceConnectionTest { when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2); final float result = mServiceConnection.getMagnificationCenterX(displayId); - assertThat(result, is(0.0f)); + assertThat(result).isEqualTo(0.0f); } @Test @@ -654,7 +667,7 @@ public class AbstractAccessibilityServiceConnectionTest { when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2); final float result = mServiceConnection.getMagnificationCenterY(displayId); - assertThat(result, is(0.0f)); + assertThat(result).isEqualTo(0.0f); } @Test @@ -664,7 +677,7 @@ public class AbstractAccessibilityServiceConnectionTest { true); final boolean result = mServiceConnection.resetMagnification(displayId, true); - assertThat(result, is(true)); + assertThat(result).isTrue(); } @Test @@ -675,7 +688,7 @@ public class AbstractAccessibilityServiceConnectionTest { when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false); final boolean result = mServiceConnection.resetMagnification(displayId, true); - assertThat(result, is(false)); + assertThat(result).isFalse(); } @Test @@ -686,7 +699,7 @@ public class AbstractAccessibilityServiceConnectionTest { when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2); final boolean result = mServiceConnection.resetMagnification(displayId, true); - assertThat(result, is(false)); + assertThat(result).isFalse(); } @Test @@ -765,6 +778,134 @@ public class AbstractAccessibilityServiceConnectionTest { == bundle.getInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS))); } + private void setPreinstalledA11yTool(boolean isPreinstalledA11yTool) { + when(mSpyServiceInfo.getResolveInfo().serviceInfo.applicationInfo.isSystemApp()) + .thenReturn(isPreinstalledA11yTool); + when(mSpyServiceInfo.isAccessibilityTool()).thenReturn(isPreinstalledA11yTool); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_SECURE_SCREENSHOTS) + public void takeScreenshot_standardService_cannotCaptureSecureLayers() { + setPreinstalledA11yTool(false); + + takeScreenshotOfDisplay(); + + final ArgumentCaptor<ScreenCapture.CaptureArgs> displayArgsCaptor = + ArgumentCaptor.forClass(ScreenCapture.CaptureArgs.class); + verify(mMockWindowManagerInternal).captureDisplay( + eq(Display.DEFAULT_DISPLAY), displayArgsCaptor.capture(), any()); + assertThat(displayArgsCaptor.getValue().mCaptureSecureLayers).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_SECURE_SCREENSHOTS) + public void takeScreenshot_preinstalledA11yTool_canCaptureSecureLayers() { + setPreinstalledA11yTool(true); + + takeScreenshotOfDisplay(); + + final ArgumentCaptor<ScreenCapture.CaptureArgs> displayArgsCaptor = + ArgumentCaptor.forClass(ScreenCapture.CaptureArgs.class); + verify(mMockWindowManagerInternal).captureDisplay( + anyInt(), displayArgsCaptor.capture(), any()); + assertThat(displayArgsCaptor.getValue().mCaptureSecureLayers).isTrue(); + } + + private void takeScreenshotOfDisplay() { + when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true); + when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(true); + + final DisplayManager displayManager = new DisplayManager(mMockContext); + when(mMockContext.getSystemService(Context.DISPLAY_SERVICE)).thenReturn(displayManager); + + mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY, + new RemoteCallback(mMockListener)); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_SECURE_SCREENSHOTS) + public void takeScreenshotOfWindow_standardWindow_standardService_cannotCaptureSecureLayers() + throws Exception { + setPreinstalledA11yTool(false); + + takeScreenshotOfWindow(/*windowFlags=*/0); + + // Screenshot was allowed + final ArgumentCaptor<ScreenCapture.LayerCaptureArgs> layerArgsCaptor = + ArgumentCaptor.forClass(ScreenCapture.LayerCaptureArgs.class); + verify(mMockSystemSupport).performScreenCapture(layerArgsCaptor.capture(), any()); + // ...without secure layers included + assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isFalse(); + // No error sent to callback + verifyZeroInteractions(mMockCallback); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_SECURE_SCREENSHOTS) + public void takeScreenshotOfWindow_standardWindow_preinstalledA11yTool_canCaptureSecureLayers() + throws Exception { + setPreinstalledA11yTool(true); + + takeScreenshotOfWindow(/*windowFlags=*/0); + + // Screenshot was allowed + final ArgumentCaptor<ScreenCapture.LayerCaptureArgs> layerArgsCaptor = + ArgumentCaptor.forClass(ScreenCapture.LayerCaptureArgs.class); + verify(mMockSystemSupport).performScreenCapture(layerArgsCaptor.capture(), any()); + // ...with secure layers included + assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isTrue(); + // No error sent to callback + verifyZeroInteractions(mMockCallback); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_SECURE_SCREENSHOTS) + public void takeScreenshotOfWindow_secureWindow_standardService_sendsCallbackError() + throws Exception { + setPreinstalledA11yTool(false); + + takeScreenshotOfWindow(WindowManager.LayoutParams.FLAG_SECURE); + + // Screenshot was not allowed + verify(mMockSystemSupport, never()).performScreenCapture(any(), any()); + // Error sent to callback + verify(mMockCallback).sendTakeScreenshotOfWindowError( + AccessibilityService.ERROR_TAKE_SCREENSHOT_SECURE_WINDOW, INTERACTION_ID); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_SECURE_SCREENSHOTS) + public void takeScreenshotOfWindow_secureWindow_preinstalledA11yTool_canCaptureSecureLayers() + throws Exception { + setPreinstalledA11yTool(true); + + takeScreenshotOfWindow(WindowManager.LayoutParams.FLAG_SECURE); + + // Screenshot was allowed + final ArgumentCaptor<ScreenCapture.LayerCaptureArgs> layerArgsCaptor = + ArgumentCaptor.forClass(ScreenCapture.LayerCaptureArgs.class); + verify(mMockSystemSupport).performScreenCapture(layerArgsCaptor.capture(), any()); + // ...with secure layers included + assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isTrue(); + // No error sent to callback + verifyZeroInteractions(mMockCallback); + } + + private void takeScreenshotOfWindow(int windowFlags) throws Exception { + when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true); + when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(true); + + mServiceConnection.takeScreenshotOfWindow( + WINDOWID, INTERACTION_ID, /*listener=*/null, mMockCallback); + final ArgumentCaptor<IWindowSurfaceInfoCallback> windowSurfaceCallbackCaptor = + ArgumentCaptor.forClass(IWindowSurfaceInfoCallback.class); + verify(mMockIA11yInteractionConnection).getWindowSurfaceInfo( + windowSurfaceCallbackCaptor.capture()); + windowSurfaceCallbackCaptor.getValue().provideWindowSurfaceInfo( + windowFlags, /*appUid=*/0, new SurfaceControl()); + } + private void updateServiceInfo(AccessibilityServiceInfo serviceInfo, int eventType, int feedbackType, int flags, String[] packageNames, int notificationTimeout) { serviceInfo.eventTypes = eventType; @@ -837,8 +978,8 @@ public class AbstractAccessibilityServiceConnectionTest { ArgumentCaptor.forClass(AccessibilityNodeInfo.class); verify(mMockCallback).setFindAccessibilityNodeInfoResult(captor.capture(), eq(INTERACTION_ID)); - assertThat(captor.getValue().getActionList(), - hasItems(AccessibilityAction.ACTION_CLICK, AccessibilityAction.ACTION_EXPAND)); + assertThat(captor.getValue().getActionList()).containsAtLeast( + AccessibilityAction.ACTION_CLICK, AccessibilityAction.ACTION_EXPAND); } private static class TestAccessibilityServiceConnection diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java index 0227ef1d2dc0..0745c68fd337 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java @@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.Context; import android.platform.test.annotations.DisableFlags; @@ -326,6 +327,120 @@ public class AutoclickControllerTest { assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue(); } + @Test + public void smallJitteryMovement_doesNotTriggerClick() { + // Initial hover move to set an anchor point. + MotionEvent initialHoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 40f, + /* metaState= */ 0); + initialHoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(initialHoverMove, initialHoverMove, /* policyFlags= */ 0); + + // Get the initial scheduled click time. + long initialScheduledTime = mController.mClickScheduler.getScheduledClickTimeForTesting(); + + // Simulate small, jittery movements (all within the default slop). + MotionEvent jitteryMove1 = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 150, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 31f, // Small change in x + /* y= */ 41f, // Small change in y + /* metaState= */ 0); + jitteryMove1.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(jitteryMove1, jitteryMove1, /* policyFlags= */ 0); + + MotionEvent jitteryMove2 = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 200, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30.5f, // Small change in x + /* y= */ 39.8f, // Small change in y + /* metaState= */ 0); + jitteryMove2.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(jitteryMove2, jitteryMove2, /* policyFlags= */ 0); + + // Verify that the scheduled click time has NOT changed. + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()) + .isEqualTo(initialScheduledTime); + } + + @Test + public void singleSignificantMovement_triggersClick() { + // Initial hover move to set an anchor point. + MotionEvent initialHoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 40f, + /* metaState= */ 0); + initialHoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(initialHoverMove, initialHoverMove, /* policyFlags= */ 0); + + // Get the initial scheduled click time. + long initialScheduledTime = mController.mClickScheduler.getScheduledClickTimeForTesting(); + + // Simulate a single, significant movement (greater than the default slop). + MotionEvent significantMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 150, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 60f, // Significant change in x (30f difference) + /* y= */ 70f, // Significant change in y (30f difference) + /* metaState= */ 0); + significantMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(significantMove, significantMove, /* policyFlags= */ 0); + + // Verify that the scheduled click time has changed (click was rescheduled). + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()) + .isNotEqualTo(initialScheduledTime); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void pauseButton_flagOn_clickNotTriggeredWhenPaused() { + injectFakeMouseActionHoverMoveEvent(); + + // Pause autoclick. + AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class); + when(mockAutoclickTypePanel.isPaused()).thenReturn(true); + mController.mAutoclickTypePanel = mockAutoclickTypePanel; + + // Send hover move event. + MotionEvent hoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 0f, + /* metaState= */ 0); + hoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + + // Verify there is not a pending click. + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1); + + // Resume autoclick. + when(mockAutoclickTypePanel.isPaused()).thenReturn(false); + + // Send initial move event again. Because this is the first recorded move, a click won't be + // scheduled. + injectFakeMouseActionHoverMoveEvent(); + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1); + + // Send move again to trigger click and verify there is now a pending click. + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1); + } + private void injectFakeMouseActionHoverMoveEvent() { MotionEvent event = getFakeMotionHoverMoveEvent(); event.setSource(InputDevice.SOURCE_MOUSE); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java index 00cc7264c1d0..ba672dcd299b 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java @@ -30,6 +30,7 @@ import android.graphics.drawable.GradientDrawable; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; +import android.view.Gravity; import android.view.View; import android.view.WindowManager; import android.widget.LinearLayout; @@ -63,8 +64,11 @@ public class AutoclickTypePanelTest { private LinearLayout mDoubleClickButton; private LinearLayout mDragButton; private LinearLayout mScrollButton; + private LinearLayout mPauseButton; + private LinearLayout mPositionButton; private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK; + private boolean mPaused; private final ClickPanelControllerInterface clickPanelController = new ClickPanelControllerInterface() { @@ -74,7 +78,9 @@ public class AutoclickTypePanelTest { } @Override - public void toggleAutoclickPause() {} + public void toggleAutoclickPause(boolean paused) { + mPaused = paused; + } }; @Before @@ -91,6 +97,8 @@ public class AutoclickTypePanelTest { contentView.findViewById(R.id.accessibility_autoclick_double_click_layout); mScrollButton = contentView.findViewById(R.id.accessibility_autoclick_scroll_layout); mDragButton = contentView.findViewById(R.id.accessibility_autoclick_drag_layout); + mPauseButton = contentView.findViewById(R.id.accessibility_autoclick_pause_layout); + mPositionButton = contentView.findViewById(R.id.accessibility_autoclick_position_layout); } @Test @@ -106,6 +114,7 @@ public class AutoclickTypePanelTest { assertThat(mDoubleClickButton.getVisibility()).isEqualTo(View.GONE); assertThat(mDragButton.getVisibility()).isEqualTo(View.GONE); assertThat(mScrollButton.getVisibility()).isEqualTo(View.GONE); + assertThat(mPauseButton.getVisibility()).isEqualTo(View.VISIBLE); } @Test @@ -124,6 +133,9 @@ public class AutoclickTypePanelTest { assertThat(mDoubleClickButton.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mDragButton.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mScrollButton.getVisibility()).isEqualTo(View.VISIBLE); + + // Pause button is always visible. + assertThat(mPauseButton.getVisibility()).isEqualTo(View.VISIBLE); } @Test @@ -142,6 +154,9 @@ public class AutoclickTypePanelTest { assertThat(mLeftClickButton.getVisibility()).isEqualTo(View.GONE); assertThat(mDoubleClickButton.getVisibility()).isEqualTo(View.GONE); assertThat(mDragButton.getVisibility()).isEqualTo(View.GONE); + + // Pause button is always visible. + assertThat(mPauseButton.getVisibility()).isEqualTo(View.VISIBLE); } @Test @@ -166,9 +181,50 @@ public class AutoclickTypePanelTest { assertThat(mActiveClickType).isEqualTo(AUTOCLICK_TYPE_SCROLL); } + @Test + public void moveToNextCorner_positionButton_rotatesThroughAllPositions() { + // Define all positions in sequence + int[][] expectedPositions = { + {0, Gravity.END | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90}, + {1, Gravity.START | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90}, + {2, Gravity.START | Gravity.TOP, /*x=*/ 15, /*y=*/ 30}, + {3, Gravity.END | Gravity.TOP, /*x=*/ 15, /*y=*/ 30}, + {0, Gravity.END | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90} + }; + + // Check initial position + verifyPanelPosition(expectedPositions[0]); + + // Move through all corners. + for (int i = 1; i < expectedPositions.length; i++) { + mPositionButton.callOnClick(); + verifyPanelPosition(expectedPositions[i]); + } + } + + @Test + public void pauseButton_onClick() { + mPauseButton.callOnClick(); + assertThat(mPaused).isTrue(); + assertThat(mAutoclickTypePanel.isPaused()).isTrue(); + + mPauseButton.callOnClick(); + assertThat(mPaused).isFalse(); + assertThat(mAutoclickTypePanel.isPaused()).isFalse(); + } + private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) { GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground(); assertThat(gradientDrawable.getColor().getDefaultColor()) .isEqualTo(mTestableContext.getColor(R.color.materialColorPrimary)); } + + private void verifyPanelPosition(int[] expectedPosition) { + WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParams(); + assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()).isEqualTo( + expectedPosition[0]); + assertThat(params.gravity).isEqualTo(expectedPosition[1]); + assertThat(params.x).isEqualTo(expectedPosition[2]); + assertThat(params.y).isEqualTo(expectedPosition[3]); + } } diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java index b5a538fa09f8..c7da27420cbb 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java @@ -103,7 +103,7 @@ public class AudioDeviceInventoryTest { // NOTE: for now this is only when flag asDeviceConnectionFailure is true if (asDeviceConnectionFailure()) { when(mSpyAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE, - AudioSystem.AUDIO_FORMAT_DEFAULT)) + AudioSystem.AUDIO_FORMAT_DEFAULT, false /*deviceSwitch*/)) .thenReturn(AudioSystem.AUDIO_STATUS_ERROR); runWithBluetoothPrivilegedPermission( () -> mDevInventory.onSetBtActiveDevice(/*btInfo*/ btInfo, @@ -115,7 +115,7 @@ public class AudioDeviceInventoryTest { // test that the device is added when AudioSystem returns AUDIO_STATUS_OK // when setDeviceConnectionState is called for the connection when(mSpyAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE, - AudioSystem.AUDIO_FORMAT_DEFAULT)) + AudioSystem.AUDIO_FORMAT_DEFAULT, false /*deviceSwitch*/)) .thenReturn(AudioSystem.AUDIO_STATUS_OK); runWithBluetoothPrivilegedPermission( () -> mDevInventory.onSetBtActiveDevice(/*btInfo*/ btInfo, diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java index ce59a86c6ca3..39e7d727f7c5 100644 --- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java +++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java @@ -51,9 +51,9 @@ public class NoOpAudioSystemAdapter extends AudioSystemAdapter { // Overrides of AudioSystemAdapter @Override public int setDeviceConnectionState(AudioDeviceAttributes attributes, int state, - int codecFormat) { - Log.i(TAG, String.format("setDeviceConnectionState(0x%s, %d, 0x%s", - attributes.toString(), state, Integer.toHexString(codecFormat))); + int codecFormat, boolean deviceSwitch) { + Log.i(TAG, String.format("setDeviceConnectionState(0x%s, %d, 0x%s %b", + attributes.toString(), state, Integer.toHexString(codecFormat), deviceSwitch)); return AudioSystem.AUDIO_STATUS_OK; } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index e5fac7ac5e0c..00b0c558b4e3 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -263,6 +263,11 @@ public class DpmMockContext extends MockContext { } @Override + public Context getApplicationContext() { + return this; + } + + @Override public PackageManager getPackageManager() { return mMockSystemServices.packageManager; } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java index fca0cfbc7d2f..b2d48a77386f 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; +import static android.content.pm.PackageManager.FEATURE_HDMI_CEC; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP; @@ -25,6 +26,7 @@ import static com.android.server.hdmi.HdmiCecFeatureAction.DELAY_GIVE_AUDIO_STAT import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -122,6 +124,9 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { @Before public void setUp() throws RemoteException { + assumeTrue("Test requires FEATURE_HDMI_CEC", + InstrumentationRegistry.getTargetContext().getPackageManager() + .hasSystemFeature(FEATURE_HDMI_CEC)); MockitoAnnotations.initMocks(this); mContextSpy = spy(new ContextWrapper( @@ -432,7 +437,7 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { .setMaxVolumeIndex(AudioStatus.MAX_VOLUME) .setMinVolumeIndex(AudioStatus.MIN_VOLUME) .build()), - any(), any(), anyBoolean()); + anyBoolean(), any(), any()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java index ec44a918f8e8..f44517a47f55 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java @@ -112,7 +112,7 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav .setMaxVolumeIndex(AudioStatus.MAX_VOLUME) .setMinVolumeIndex(AudioStatus.MIN_VOLUME) .build()), - any(), any(), anyBoolean()); + anyBoolean(), any(), any()); } @@ -135,7 +135,7 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav .setMaxVolumeIndex(AudioStatus.MAX_VOLUME) .setMinVolumeIndex(AudioStatus.MIN_VOLUME) .build()), - any(), any(), anyBoolean()); + anyBoolean(), any(), any()); } @Test @@ -160,7 +160,7 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav .setMaxVolumeIndex(AudioStatus.MAX_VOLUME) .setMinVolumeIndex(AudioStatus.MIN_VOLUME) .build()), - any(), any(), anyBoolean()); + anyBoolean(), any(), any()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java index 7294ba62cdae..90f94cb4b596 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java @@ -183,9 +183,9 @@ public class FakeAudioFramework { public void setDeviceAbsoluteVolumeBehavior( @NonNull AudioDeviceAttributes device, @NonNull VolumeInfo volume, + boolean handlesVolumeAdjustment, @NonNull @CallbackExecutor Executor executor, - @NonNull OnAudioDeviceVolumeChangedListener vclistener, - boolean handlesVolumeAdjustment) { + @NonNull OnAudioDeviceVolumeChangedListener vclistener) { setVolumeBehaviorHelper(device, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); } @@ -193,9 +193,9 @@ public class FakeAudioFramework { public void setDeviceAbsoluteVolumeAdjustOnlyBehavior( @NonNull AudioDeviceAttributes device, @NonNull VolumeInfo volume, + boolean handlesVolumeAdjustment, @NonNull @CallbackExecutor Executor executor, - @NonNull OnAudioDeviceVolumeChangedListener vclistener, - boolean handlesVolumeAdjustment) { + @NonNull OnAudioDeviceVolumeChangedListener vclistener) { setVolumeBehaviorHelper(device, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index f74e2ace7ae3..563baacf5811 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -66,6 +66,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -1033,6 +1034,7 @@ public class HdmiCecLocalDeviceTvTest { } @Test + @Ignore("b/360768278") public void onHotplug_doNotSend_systemAudioModeRequestWithParameter(){ // Add a device to the network and assert that this device is included in the list of // devices. diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index 87c9db2fe565..acbce36c3d7f 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -354,14 +354,20 @@ public abstract class BaseLockSettingsServiceTests { @After public void tearDown_baseServices() throws Exception { - mStorage.closeDatabase(); + if (mStorage != null) { + mStorage.closeDatabase(); + } File db = InstrumentationRegistry.getContext().getDatabasePath("locksettings.db"); assertTrue(!db.exists() || db.delete()); - File storageDir = mStorage.mStorageDir; - assertTrue(FileUtils.deleteContents(storageDir)); + if (mStorage != null) { + File storageDir = mStorage.mStorageDir; + assertTrue(FileUtils.deleteContents(storageDir)); + } - mPasswordSlotManager.cleanup(); + if (mPasswordSlotManager != null) { + mPasswordSlotManager.cleanup(); + } } protected void flushHandlerTasks() { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java index 02b86db6ab6f..387b89a41eba 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java @@ -124,7 +124,9 @@ public class LockSettingsStorageTests { @After public void tearDown() throws Exception { - mStorage.closeDatabase(); + if (mStorage != null) { + mStorage.closeDatabase(); + } } @Test diff --git a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java index 2faf6a2b29d1..2c2b9374fdf9 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java @@ -49,7 +49,9 @@ public class PasswordSlotManagerTests { @After public void tearDown() throws Exception { - mManager.cleanup(); + if (mManager != null) { + mManager.cleanup(); + } } @Test diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java index 1514de04fb08..5add74e5b69e 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java @@ -156,9 +156,12 @@ public class KeySyncTaskTest { @After public void tearDown() { - mRecoverableKeyStoreDb.close(); - mDatabaseFile.delete(); - + if (mRecoverableKeyStoreDb != null) { + mRecoverableKeyStoreDb.close(); + } + if (mDatabaseFile != null) { + mDatabaseFile.delete(); + } File file = new File(InstrumentationRegistry.getTargetContext().getFilesDir(), SNAPSHOT_TOP_LEVEL_DIRECTORY); FileUtils.deleteContentsAndDir(file); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java index c09e09c8404f..46eaba7dace6 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java @@ -117,8 +117,12 @@ public class PlatformKeyManagerTest { @After public void tearDown() { - mRecoverableKeyStoreDb.close(); - mDatabaseFile.delete(); + if (mRecoverableKeyStoreDb != null) { + mRecoverableKeyStoreDb.close(); + } + if (mDatabaseFile != null) { + mDatabaseFile.delete(); + } } @Test diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java index 64130266b2c4..e6a6e36e75d6 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java @@ -89,8 +89,12 @@ public class RecoverableKeyGeneratorTest { keyStore.load(/*param=*/ null); keyStore.deleteEntry(WRAPPING_KEY_ALIAS); - mRecoverableKeyStoreDb.close(); - mDatabaseFile.delete(); + if (mRecoverableKeyStoreDb != null) { + mRecoverableKeyStoreDb.close(); + } + if (mDatabaseFile != null) { + mDatabaseFile.delete(); + } } @Test diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 7641fb957cc8..878c838e734b 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -230,9 +230,15 @@ public class RecoverableKeyStoreManagerTest { @After public void tearDown() { - mRemoteLockscreenValidationSessionStorage.finishSession(mUserId); - mRecoverableKeyStoreDb.close(); - mDatabaseFile.delete(); + if (mRemoteLockscreenValidationSessionStorage != null) { + mRemoteLockscreenValidationSessionStorage.finishSession(mUserId); + } + if (mRecoverableKeyStoreDb != null) { + mRecoverableKeyStoreDb.close(); + } + if (mDatabaseFile != null) { + mDatabaseFile.delete(); + } } @Test diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java index bbd9223718ae..fb98fab52ca0 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java @@ -18,6 +18,8 @@ package com.android.server.locksettings.recoverablekeystore.storage; import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; + import android.content.ContentValues; import android.content.Context; import android.database.sqlite.SQLiteDatabase; @@ -36,8 +38,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import static java.nio.charset.StandardCharsets.UTF_8; - @SmallTest @RunWith(AndroidJUnit4.class) public class RecoverableKeyStoreDbHelperTest { @@ -110,7 +110,9 @@ public class RecoverableKeyStoreDbHelperTest { @After public void tearDown() throws Exception { - mDatabase.close(); + if (mDatabase != null) { + mDatabase.close(); + } } private void createV2Tables() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java index 8bc14fc54ae1..a77d8bcd3875 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java @@ -72,8 +72,12 @@ public class RecoverableKeyStoreDbTest { @After public void tearDown() { - mRecoverableKeyStoreDb.close(); - mDatabaseFile.delete(); + if (mRecoverableKeyStoreDb != null) { + mRecoverableKeyStoreDb.close(); + } + if (mDatabaseFile != null) { + mDatabaseFile.delete(); + } } @Test diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayConstraintsTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayConstraintsTests.java index b2e296a36b93..2912a0762761 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayConstraintsTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayConstraintsTests.java @@ -19,6 +19,7 @@ package com.android.server.om; import static android.content.Context.DEVICE_ID_DEFAULT; import static android.content.om.OverlayConstraint.TYPE_DEVICE_ID; import static android.content.om.OverlayConstraint.TYPE_DISPLAY_ID; +import static android.util.TypedValue.TYPE_STRING; import static android.view.Display.DEFAULT_DISPLAY; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; @@ -28,6 +29,9 @@ import static junit.framework.Assert.assertNotNull; import static org.testng.Assert.assertThrows; +import android.app.Activity; +import android.companion.virtual.VirtualDeviceManager; +import android.content.Context; import android.content.om.FabricatedOverlay; import android.content.om.OverlayConstraint; import android.content.om.OverlayIdentifier; @@ -35,12 +39,12 @@ import android.content.om.OverlayInfo; import android.content.om.OverlayManager; import android.content.om.OverlayManagerTransaction; import android.content.res.Flags; +import android.content.res.Resources; import android.os.UserHandle; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.util.TypedValue; +import android.view.Display; +import android.virtualdevice.cts.common.VirtualDeviceRule; import junitparams.JUnitParamsRunner; import junitparams.Parameters; @@ -53,20 +57,28 @@ import org.junit.runner.RunWith; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeoutException; @RunWith(JUnitParamsRunner.class) public class OverlayConstraintsTests { + private static final String RESOURCE_NAME = "string/module_2_name"; + private static final String RESOURCE_DEFAULT_VALUE = "module_2_name"; + private static final String RESOURCE_OVERLAID_VALUE = "hello"; + private static final long TIMEOUT_MILLIS = 2000L; @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault(); private OverlayManager mOverlayManager; private UserHandle mUserHandle; private OverlayIdentifier mOverlayIdentifier = null; + private final String mPackageName = getApplicationContext().getPackageName(); @Before public void setUp() throws Exception { - mOverlayManager = getApplicationContext().getSystemService(OverlayManager.class); + final Context context = getApplicationContext(); + mOverlayManager = context.getSystemService(OverlayManager.class); mUserHandle = UserHandle.of(UserHandle.myUserId()); } @@ -79,6 +91,7 @@ public class OverlayConstraintsTests { .build(); mOverlayManager.commit(transaction); mOverlayIdentifier = null; + waitForResourceValue(RESOURCE_DEFAULT_VALUE, getApplicationContext()); } } @@ -161,13 +174,161 @@ public class OverlayConstraintsTests { List.of(new OverlayConstraint(TYPE_DISPLAY_ID, DEFAULT_DISPLAY)))); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS) + public void enableOverlayWithoutConstraints_appliesOverlayWithoutConstraints() + throws Exception { + enableOverlay(Collections.emptyList()); + + // Assert than the overlay is applied for both default device context and virtual + // device context. + final Context context = getApplicationContext(); + waitForResourceValue(RESOURCE_OVERLAID_VALUE, context); + VirtualDeviceManager.VirtualDevice virtualDevice = + mVirtualDeviceRule.createManagedVirtualDevice(); + final Context deviceContext = context.createDeviceContext(virtualDevice.getDeviceId()); + waitForResourceValue(RESOURCE_OVERLAID_VALUE, deviceContext); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS) + public void enableOverlayWithConstraints_withTypeDeviceId_appliesOverlayWithConstraints() + throws Exception { + final int deviceId1 = mVirtualDeviceRule.createManagedVirtualDevice().getDeviceId(); + final int deviceId2 = mVirtualDeviceRule.createManagedVirtualDevice().getDeviceId(); + enableOverlay(List.of(new OverlayConstraint(TYPE_DEVICE_ID, deviceId1), + new OverlayConstraint(TYPE_DEVICE_ID, deviceId2))); + + // Assert than the overlay is not applied for contexts not associated with the above + // devices. + final Context context = getApplicationContext(); + ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, context); + final int deviceId3 = mVirtualDeviceRule.createManagedVirtualDevice().getDeviceId(); + ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, context.createDeviceContext(deviceId3)); + + // Assert than the overlay is applied for contexts associated with the above devices. + waitForResourceValue(RESOURCE_OVERLAID_VALUE, context.createDeviceContext(deviceId1)); + waitForResourceValue(RESOURCE_OVERLAID_VALUE, context.createDeviceContext(deviceId2)); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS) + public void enableOverlayWithConstraints_withTypeDisplayId_appliesOverlayWithConstraints() + throws Exception { + final Display display1 = + mVirtualDeviceRule.createManagedUnownedVirtualDisplay().getDisplay(); + final Display display2 = + mVirtualDeviceRule.createManagedUnownedVirtualDisplay().getDisplay(); + enableOverlay(List.of(new OverlayConstraint(TYPE_DISPLAY_ID, display1.getDisplayId()), + new OverlayConstraint(TYPE_DISPLAY_ID, display2.getDisplayId()))); + + // Assert than the overlay is not applied for contexts not associated with the above + // displays. + final Context context = getApplicationContext(); + ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, context); + final Display display3 = + mVirtualDeviceRule.createManagedUnownedVirtualDisplay().getDisplay(); + ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, context.createDisplayContext(display3)); + + // Assert than the overlay is applied for contexts associated with the above displays. + waitForResourceValue(RESOURCE_OVERLAID_VALUE, context.createDisplayContext(display1)); + waitForResourceValue(RESOURCE_OVERLAID_VALUE, context.createDisplayContext(display2)); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS) + public void enableOverlayWithConstraints_withTypesDisplayIdAndDeviceId_appliesOverlayWithConstraints() + throws Exception { + final Display display1 = + mVirtualDeviceRule.createManagedUnownedVirtualDisplay().getDisplay(); + final int deviceId1 = mVirtualDeviceRule.createManagedVirtualDevice().getDeviceId(); + enableOverlay(List.of(new OverlayConstraint(TYPE_DISPLAY_ID, display1.getDisplayId()), + new OverlayConstraint(TYPE_DEVICE_ID, deviceId1))); + + // Assert than the overlay is not applied for contexts not associated with the above + // display or device. + final Context context = getApplicationContext(); + ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, context); + final Display display2 = + mVirtualDeviceRule.createManagedUnownedVirtualDisplay().getDisplay(); + ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, context.createDisplayContext(display2)); + final int deviceId2 = mVirtualDeviceRule.createManagedVirtualDevice().getDeviceId(); + ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, context.createDeviceContext(deviceId2)); + + // Assert than the overlay is applied for contexts associated with the above display or + // device. + waitForResourceValue(RESOURCE_OVERLAID_VALUE, context.createDisplayContext(display1)); + waitForResourceValue(RESOURCE_OVERLAID_VALUE, context.createDeviceContext(deviceId1)); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS) + public void enableOverlayWithConstraints_withTypeDisplayId_appliesForActivityOnDisplay() + throws Exception { + final Display display = + mVirtualDeviceRule.createManagedUnownedVirtualDisplay( + VirtualDeviceRule.createTrustedVirtualDisplayConfigBuilder()) + .getDisplay(); + final Activity activityOnDefaultDisplay = mVirtualDeviceRule.startActivityOnDisplaySync( + DEFAULT_DISPLAY, Activity.class); + final Activity activityOnVirtualDisplay = mVirtualDeviceRule.startActivityOnDisplaySync( + display.getDisplayId(), Activity.class); + + enableOverlay(List.of(new OverlayConstraint(TYPE_DISPLAY_ID, display.getDisplayId()))); + + // Assert than the overlay is not applied for any existing activity on the default display. + ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, activityOnDefaultDisplay); + // Assert than the overlay is applied for any existing activity on the virtual display. + waitForResourceValue(RESOURCE_OVERLAID_VALUE, activityOnVirtualDisplay); + + // Assert than the overlay is not applied for any new activity on the default display. + final Activity newActivityOnDefaultDisplay = mVirtualDeviceRule.startActivityOnDisplaySync( + DEFAULT_DISPLAY, Activity.class); + ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, newActivityOnDefaultDisplay); + // Assert than the overlay is applied for any new activity on the virtual display. + final Activity newActivityOnVirtualDisplay = mVirtualDeviceRule.startActivityOnDisplaySync( + display.getDisplayId(), Activity.class); + waitForResourceValue(RESOURCE_OVERLAID_VALUE, newActivityOnVirtualDisplay); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS) + public void enableOverlayWithConstraints_withTypeDeviceId_appliesForActivityOnDevice() + throws Exception { + final VirtualDeviceManager.VirtualDevice device = + mVirtualDeviceRule.createManagedVirtualDevice(); + final Display display = + mVirtualDeviceRule.createManagedVirtualDisplay(device, + VirtualDeviceRule.createTrustedVirtualDisplayConfigBuilder()) + .getDisplay(); + final Activity activityOnDefaultDevice = mVirtualDeviceRule.startActivityOnDisplaySync( + DEFAULT_DISPLAY, Activity.class); + final Activity activityOnVirtualDevice = mVirtualDeviceRule.startActivityOnDisplaySync( + display.getDisplayId(), Activity.class); + + enableOverlay(List.of(new OverlayConstraint(TYPE_DEVICE_ID, device.getDeviceId()))); + + // Assert than the overlay is not applied for any existing activity on the default device. + ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, activityOnDefaultDevice); + // Assert than the overlay is applied for any existing activity on the virtual device. + waitForResourceValue(RESOURCE_OVERLAID_VALUE, activityOnVirtualDevice); + + // Assert than the overlay is not applied for any new activity on the default device. + final Activity newActivityOnDefaultDevice = mVirtualDeviceRule.startActivityOnDisplaySync( + DEFAULT_DISPLAY, Activity.class); + ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, newActivityOnDefaultDevice); + // Assert than the overlay is applied for any new activity on the virtual device. + final Activity newActivityOnVirtualDevice = mVirtualDeviceRule.startActivityOnDisplaySync( + display.getDisplayId(), Activity.class); + waitForResourceValue(RESOURCE_OVERLAID_VALUE, newActivityOnVirtualDevice); + } + private FabricatedOverlay createFabricatedOverlay() { - String packageName = getApplicationContext().getPackageName(); FabricatedOverlay fabricatedOverlay = new FabricatedOverlay.Builder( - packageName, "testOverlay" /* name */, packageName) + mPackageName, "testOverlay" /* name */, mPackageName) .build(); - fabricatedOverlay.setResourceValue("string/module_2_name" /* resourceName */, - TypedValue.TYPE_STRING, "hello" /* value */, null /* configuration */); + fabricatedOverlay.setResourceValue(RESOURCE_NAME, TYPE_STRING, RESOURCE_OVERLAID_VALUE, + null /* configuration */); return fabricatedOverlay; } @@ -183,6 +344,37 @@ public class OverlayConstraintsTests { mOverlayIdentifier = fabricatedOverlay.getIdentifier(); } + private static void waitForResourceValue(final String expectedValue, Context context) + throws TimeoutException { + final long endTime = System.currentTimeMillis() + TIMEOUT_MILLIS; + final Resources resources = context.getResources(); + final int resourceId = getResourceId(context); + String resourceValue = null; + while (System.currentTimeMillis() < endTime) { + resourceValue = resources.getString(resourceId); + if (Objects.equals(resourceValue, expectedValue)) { + return; + } + } + throw new TimeoutException("Timed out waiting for '" + RESOURCE_NAME + "' value to equal '" + + expectedValue + "': current value is '" + resourceValue + "'"); + } + + private static void ensureResourceValueStaysAt(final String expectedValue, Context context) { + final long endTime = System.currentTimeMillis() + TIMEOUT_MILLIS; + final Resources resources = context.getResources(); + final int resourceId = getResourceId(context); + String resourceValue; + while (System.currentTimeMillis() < endTime) { + resourceValue = resources.getString(resourceId); + assertEquals(expectedValue, resourceValue); + } + } + + private static int getResourceId(Context context) { + return context.getResources().getIdentifier(RESOURCE_NAME, "", context.getPackageName()); + } + private static List<OverlayConstraint>[] getAllConstraintLists() { return new List[]{ Collections.emptyList(), diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 3ef360a752f6..da14e451d656 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -95,8 +95,8 @@ import android.test.mock.MockContext; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; +import android.util.SparseArray; -import com.android.internal.infra.AndroidFuture; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.pm.LauncherAppsService.LauncherAppsImpl; @@ -110,6 +110,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.BufferedReader; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -149,6 +150,9 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected static final String MAIN_ACTIVITY_CLASS = "MainActivity"; protected static final String PIN_CONFIRM_ACTIVITY_CLASS = "PinConfirmActivity"; + private byte[] mBaseState; + protected final SparseArray<byte[]> mUserStates = new SparseArray<>(); + // public for mockito public class BaseContext extends MockContext { @Override @@ -287,6 +291,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { final ServiceContext mContext; IUidObserver mUidObserver; + public ShortcutServiceTestable(ServiceContext context, Looper looper) { super(context, looper, /* onyForPackageManagerApis */ false); mContext = context; @@ -567,6 +572,58 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { // During tests, WTF is fatal. fail(message + " exception: " + th + "\n" + Log.getStackTraceString(th)); } + + @Override + void injectSaveBaseState() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + saveBaseStateAsXml(baos); + } catch (Exception e) { + throw new RuntimeException(e); + } + mBaseState = baos.toByteArray(); + } + + @Override + protected void injectLoadBaseState() { + if (mBaseState == null) { + return; + } + ByteArrayInputStream bais = new ByteArrayInputStream(mBaseState); + try { + loadBaseStateAsXml(bais); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + protected void injectSaveUser(@UserIdInt int userId) { + synchronized (mServiceLock) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + saveUserInternalLocked(userId, baos, /* forBackup= */ false); + cleanupDanglingBitmapDirectoriesLocked(userId); + } catch (Exception e) { + throw new RuntimeException(e); + } + mUserStates.put(userId, baos.toByteArray()); + } + } + + @Override + protected ShortcutUser injectLoadUserLocked(@UserIdInt int userId) { + final byte[] userState = mUserStates.get(userId); + if (userState == null) { + return null; + } + ByteArrayInputStream bais = new ByteArrayInputStream(userState); + try { + return loadUserInternal(userId, bais, /* forBackup= */ false); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } /** ShortcutManager with injection override methods. */ diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index f536cae53e3a..58f762204c27 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -201,7 +201,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50; assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL); - mService.saveBaseState(); + mService.injectSaveBaseState(); dumpBaseStateFile(); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 3b32701b5169..f5690b77d2fe 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -54,9 +54,7 @@ import androidx.test.filters.SmallTest; import com.android.frameworks.servicestests.R; import java.io.File; -import java.io.FileWriter; import java.io.IOException; -import java.io.Writer; import java.util.Locale; /** @@ -2358,12 +2356,10 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { * can still be read. */ public void testLoadLegacySavedFile() throws Exception { - final File path = mService.getUserFile(USER_10).getBaseFile(); - path.getParentFile().mkdirs(); - try (Writer w = new FileWriter(path)) { - w.write(readTestAsset("shortcut/shortcut_legacy_file.xml")); - }; + final String legacyFile = readTestAsset("shortcut/shortcut_legacy_file.xml"); + mUserStates.put(USER_10, legacyFile.getBytes()); initService(); + mService.handleUnlockUser(USER_10); runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp index 0eb20eb22380..66d7611a29c6 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -32,6 +32,7 @@ android_test { "androidx.test.rules", "hamcrest-library", "mockito-target-inline-minus-junit4", + "mockito-target-extended", "platform-compat-test-rules", "platform-test-annotations", "platformprotosnano", diff --git a/services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java b/services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java index 779fa1aa2f72..dbbe40fd42e6 100644 --- a/services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java +++ b/services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java @@ -80,7 +80,7 @@ public class NotificationManagerZenTest { } @Test - @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + @RequiresFlagsEnabled(Flags.FLAG_MODES_UI) public void setAutomaticZenRuleState_manualActivation() { AutomaticZenRule ruleToCreate = createZenRule("rule"); String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate); @@ -111,7 +111,7 @@ public class NotificationManagerZenTest { } @Test - @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + @RequiresFlagsEnabled(Flags.FLAG_MODES_UI) public void setAutomaticZenRuleState_manualDeactivation() { AutomaticZenRule ruleToCreate = createZenRule("rule"); String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate); @@ -145,7 +145,7 @@ public class NotificationManagerZenTest { } @Test - @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + @RequiresFlagsEnabled(Flags.FLAG_MODES_UI) public void setAutomaticZenRuleState_respectsManuallyActivated() { AutomaticZenRule ruleToCreate = createZenRule("rule"); String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate); @@ -178,7 +178,7 @@ public class NotificationManagerZenTest { } @Test - @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + @RequiresFlagsEnabled(Flags.FLAG_MODES_UI) public void setAutomaticZenRuleState_respectsManuallyDeactivated() { AutomaticZenRule ruleToCreate = createZenRule("rule"); String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate); @@ -212,7 +212,7 @@ public class NotificationManagerZenTest { } @Test - @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + @RequiresFlagsEnabled(Flags.FLAG_MODES_UI) public void setAutomaticZenRuleState_manualActivationFromApp() { AutomaticZenRule ruleToCreate = createZenRule("rule"); String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate); @@ -244,7 +244,7 @@ public class NotificationManagerZenTest { } @Test - @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + @RequiresFlagsEnabled(Flags.FLAG_MODES_UI) public void setAutomaticZenRuleState_manualDeactivationFromApp() { AutomaticZenRule ruleToCreate = createZenRule("rule"); String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate); diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index c4b8599a483c..9930c9f07ed8 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -70,7 +70,6 @@ import android.Manifest; import android.app.Activity; import android.app.ActivityManager; import android.app.AlarmManager; -import android.app.Flags; import android.app.IOnProjectionStateChangedListener; import android.app.IUiModeManager; import android.content.BroadcastReceiver; @@ -91,7 +90,6 @@ import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.os.test.FakePermissionEnforcer; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.service.dreams.DreamManagerInternal; @@ -1508,13 +1506,11 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(Flags.FLAG_MODES_API) public void testAttentionModeThemeOverlay_nightModeDisabled() throws RemoteException { testAttentionModeThemeOverlay(false); } @Test - @EnableFlags(Flags.FLAG_MODES_API) public void testAttentionModeThemeOverlay_nightModeEnabled() throws RemoteException { testAttentionModeThemeOverlay(true); } diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java index b3ec2153542a..c9d5241c57b7 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java +++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java @@ -30,6 +30,7 @@ import android.testing.TestableContext; import androidx.test.InstrumentationRegistry; +import com.android.server.pm.UserManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; import org.junit.After; @@ -41,6 +42,7 @@ import org.mockito.MockitoAnnotations; public class UiServiceTestCase { @Mock protected PackageManagerInternal mPmi; + @Mock protected UserManagerInternal mUmi; @Mock protected UriGrantsManagerInternal mUgmInternal; protected static final String PKG_N_MR1 = "com.example.n_mr1"; @@ -92,6 +94,8 @@ public class UiServiceTestCase { } }); + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mUmi); LocalServices.removeServiceForTest(UriGrantsManagerInternal.class); LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal); when(mUgmInternal.checkGrantUriPermission( diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java index 1890879da69d..5ce9a3e8d4d4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java @@ -47,7 +47,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.hardware.display.ColorDisplayManager; import android.os.PowerManager; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.ZenDeviceEffects; import android.testing.TestableContext; @@ -102,8 +101,6 @@ public class DefaultDeviceEffectsApplierTest { @Test public void apply_appliesEffects() { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); - ZenDeviceEffects effects = new ZenDeviceEffects.Builder() .setShouldSuppressAmbientDisplay(true) .setShouldDimWallpaper(true) @@ -119,7 +116,6 @@ public class DefaultDeviceEffectsApplierTest { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void apply_logsToZenLog() { when(mPowerManager.isInteractive()).thenReturn(true); ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = @@ -155,8 +151,6 @@ public class DefaultDeviceEffectsApplierTest { @Test public void apply_removesEffects() { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); - ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder() .setShouldSuppressAmbientDisplay(true) .setShouldDimWallpaper(true) @@ -180,8 +174,6 @@ public class DefaultDeviceEffectsApplierTest { @Test public void apply_removesOnlyPreviouslyAppliedEffects() { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); - ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder() .setShouldSuppressAmbientDisplay(true) .build(); @@ -197,7 +189,6 @@ public class DefaultDeviceEffectsApplierTest { @Test public void apply_missingSomeServices_okay() { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mContext.addMockSystemService(ColorDisplayManager.class, null); mContext.addMockSystemService(WallpaperManager.class, null); mApplier = new DefaultDeviceEffectsApplier(mContext); @@ -216,7 +207,6 @@ public class DefaultDeviceEffectsApplierTest { @Test public void apply_disabledWallpaperService_dimWallpaperNotApplied() { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); WallpaperManager disabledWallpaperService = mock(WallpaperManager.class); when(mWallpaperManager.isWallpaperSupported()).thenReturn(false); mContext.addMockSystemService(WallpaperManager.class, disabledWallpaperService); @@ -236,8 +226,6 @@ public class DefaultDeviceEffectsApplierTest { @Test public void apply_someEffects_onlyThoseEffectsApplied() { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); - ZenDeviceEffects effects = new ZenDeviceEffects.Builder() .setShouldDimWallpaper(true) .setShouldDisplayGrayscale(true) @@ -253,8 +241,6 @@ public class DefaultDeviceEffectsApplierTest { @Test public void apply_onlyEffectDeltaApplied() { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); - mApplier.apply(new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build(), ORIGIN_USER_IN_SYSTEMUI); verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); @@ -272,7 +258,6 @@ public class DefaultDeviceEffectsApplierTest { @Test public void apply_nightModeFromApp_appliedOnScreenOff() { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); ArgumentCaptor<IntentFilter> intentFilterCaptor = @@ -301,8 +286,6 @@ public class DefaultDeviceEffectsApplierTest { @Test public void apply_nightModeWithScreenOff_appliedImmediately( @TestParameter ZenChangeOrigin origin) { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); - when(mPowerManager.isInteractive()).thenReturn(false); mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), @@ -314,7 +297,6 @@ public class DefaultDeviceEffectsApplierTest { } @Test - @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI}) public void apply_nightModeWithScreenOnAndKeyguardShowing_appliedImmediately( @TestParameter ZenChangeOrigin origin) { @@ -334,8 +316,6 @@ public class DefaultDeviceEffectsApplierTest { "{origin: ORIGIN_INIT}", "{origin: ORIGIN_INIT_USER}"}) public void apply_nightModeWithScreenOn_appliedImmediatelyBasedOnOrigin( ZenChangeOrigin origin) { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); - when(mPowerManager.isInteractive()).thenReturn(true); mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), @@ -351,8 +331,6 @@ public class DefaultDeviceEffectsApplierTest { "{origin: ORIGIN_SYSTEM}", "{origin: ORIGIN_UNKNOWN}"}) public void apply_nightModeWithScreenOn_willBeAppliedLaterBasedOnOrigin( ZenChangeOrigin origin) { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); - when(mPowerManager.isInteractive()).thenReturn(true); mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), @@ -367,8 +345,6 @@ public class DefaultDeviceEffectsApplierTest { @Test public void apply_servicesThrow_noCrash() { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); - doThrow(new RuntimeException()).when(mPowerManager) .suppressAmbientDisplay(anyString(), anyBoolean()); doThrow(new RuntimeException()).when(mColorDisplayManager).setSaturationLevel(anyInt()); 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 e5c42082ab97..98440ecdad82 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -17,12 +17,17 @@ package com.android.server.notification; import static android.content.Context.DEVICE_POLICY_SERVICE; import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR; +import static android.os.UserHandle.USER_ALL; +import static android.os.UserHandle.USER_CURRENT; import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.notification.Flags.FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER; +import static com.android.server.notification.Flags.managedServicesConcurrentMultiuser; import static com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT; import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE; import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled; @@ -66,7 +71,9 @@ import android.os.IInterface; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; @@ -83,6 +90,7 @@ import com.android.server.UiServiceTestCase; import com.google.android.collect.Lists; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -105,6 +113,9 @@ import java.util.concurrent.CountDownLatch; public class ManagedServicesTest extends UiServiceTestCase { + @Rule + public SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private IPackageManager mIpm; @Mock @@ -155,6 +166,7 @@ public class ManagedServicesTest extends UiServiceTestCase { users.add(new UserInfo(11, "11", 0)); users.add(new UserInfo(12, "12", 0)); users.add(new UserInfo(13, "13", 0)); + users.add(new UserInfo(99, "99", 0)); for (UserInfo user : users) { when(mUm.getUserInfo(eq(user.id))).thenReturn(user); } @@ -804,6 +816,7 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void rebindServices_onlyBindsExactMatchesIfComponent() throws Exception { // If the primary and secondary lists contain component names, only those components within // the package should be matched @@ -841,6 +854,45 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void rebindServices_onlyBindsExactMatchesIfComponent_concurrent_multiUser() + throws Exception { + // If the primary and secondary lists contain component names, only those components within + // the package should be matched + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, + mIpm, + ManagedServices.APPROVAL_BY_COMPONENT); + + List<String> packages = new ArrayList<>(); + packages.add("package"); + packages.add("anotherPackage"); + addExpectedServices(service, packages, 0); + + // only 2 components are approved per package + mExpectedPrimaryComponentNames.clear(); + mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2"); + mExpectedSecondaryComponentNames.clear(); + mExpectedSecondaryComponentNames.put(0, "anotherPackage/C1:anotherPackage/C2"); + + loadXml(service); + // verify the 2 components per package are enabled (bound) + verifyExpectedBoundEntries(service, true, 0); + verifyExpectedBoundEntries(service, false, 0); + + // verify the last component per package is not enabled/we don't try to bind to it + for (String pkg : packages) { + ComponentName unapprovedAdditionalComponent = + ComponentName.unflattenFromString(pkg + "/C3"); + assertFalse( + service.isComponentEnabledForUser( + unapprovedAdditionalComponent, 0)); + verify(mIpm, never()).getServiceInfo( + eq(unapprovedAdditionalComponent), anyLong(), anyInt()); + } + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void rebindServices_bindsEverythingInAPackage() throws Exception { // If the primary and secondary lists contain packages, all components within those packages // should be bound @@ -866,6 +918,32 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void rebindServices_bindsEverythingInAPackage_concurrent_multiUser() throws Exception { + // If the primary and secondary lists contain packages, all components within those packages + // should be bound + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, + APPROVAL_BY_PACKAGE); + + List<String> packages = new ArrayList<>(); + packages.add("package"); + packages.add("packagea"); + addExpectedServices(service, packages, 0); + + // 2 approved packages + mExpectedPrimaryPackages.clear(); + mExpectedPrimaryPackages.put(0, "package"); + mExpectedSecondaryPackages.clear(); + mExpectedSecondaryPackages.put(0, "packagea"); + + loadXml(service); + + // verify the 3 components per package are enabled (bound) + verifyExpectedBoundEntries(service, true, 0); + verifyExpectedBoundEntries(service, false, 0); + } + + @Test public void reregisterService_checksAppIsApproved_pkg() throws Exception { Context context = mock(Context.class); PackageManager pm = mock(PackageManager.class); @@ -1118,6 +1196,7 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void testUpgradeAppBindsNewServices() throws Exception { // If the primary and secondary lists contain component names, only those components within // the package should be matched @@ -1159,6 +1238,49 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testUpgradeAppBindsNewServices_concurrent_multiUser() throws Exception { + // If the primary and secondary lists contain component names, only those components within + // the package should be matched + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, + mIpm, + ManagedServices.APPROVAL_BY_PACKAGE); + + List<String> packages = new ArrayList<>(); + packages.add("package"); + addExpectedServices(service, packages, 0); + + // only 2 components are approved per package + mExpectedPrimaryComponentNames.clear(); + mExpectedPrimaryPackages.clear(); + mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2"); + mExpectedSecondaryComponentNames.clear(); + mExpectedSecondaryPackages.clear(); + + loadXml(service); + + // new component expected + mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2:package/C3"); + + service.onPackagesChanged(false, new String[]{"package"}, new int[]{0}); + + // verify the 3 components per package are enabled (bound) + verifyExpectedBoundEntries(service, true, 0); + + // verify the last component per package is not enabled/we don't try to bind to it + for (String pkg : packages) { + ComponentName unapprovedAdditionalComponent = + ComponentName.unflattenFromString(pkg + "/C3"); + assertFalse( + service.isComponentEnabledForUser( + unapprovedAdditionalComponent, 0)); + verify(mIpm, never()).getServiceInfo( + eq(unapprovedAdditionalComponent), anyLong(), anyInt()); + } + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void testUpgradeAppNoPermissionNoRebind() throws Exception { Context context = spy(getContext()); doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any()); @@ -1211,6 +1333,59 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testUpgradeAppNoPermissionNoRebind_concurrent_multiUser() throws Exception { + Context context = spy(getContext()); + doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any()); + + ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, + mIpm, + APPROVAL_BY_COMPONENT); + + List<String> packages = new ArrayList<>(); + packages.add("package"); + addExpectedServices(service, packages, 0); + + final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1"); + final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2"); + + // Both components are approved initially + mExpectedPrimaryComponentNames.clear(); + mExpectedPrimaryPackages.clear(); + mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2"); + mExpectedSecondaryComponentNames.clear(); + mExpectedSecondaryPackages.clear(); + + loadXml(service); + + //Component package/C1 loses bind permission + when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer( + (Answer<ServiceInfo>) invocation -> { + ComponentName invocationCn = invocation.getArgument(0); + if (invocationCn != null) { + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.packageName = invocationCn.getPackageName(); + serviceInfo.name = invocationCn.getClassName(); + if (invocationCn.equals(unapprovedComponent)) { + serviceInfo.permission = "none"; + } else { + serviceInfo.permission = service.getConfig().bindPermission; + } + serviceInfo.metaData = null; + return serviceInfo; + } + return null; + } + ); + + // Trigger package update + service.onPackagesChanged(false, new String[]{"package"}, new int[]{0}); + + assertFalse(service.isComponentEnabledForUser(unapprovedComponent, 0)); + assertTrue(service.isComponentEnabledForUser(approvedComponent, 0)); + } + + @Test public void testSetPackageOrComponentEnabled() throws Exception { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, @@ -1517,6 +1692,201 @@ public class ManagedServicesTest extends UiServiceTestCase { assertTrue(componentsToBind.get(10).contains(ComponentName.unflattenFromString("c/c"))); } + @SuppressWarnings("GuardedBy") + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testPopulateComponentsToBindWithNonProfileUser() { + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + spyOn(service); + + SparseArray<ArraySet<ComponentName>> approvedComponentsByUser = new SparseArray<>(); + ArraySet<ComponentName> allowed0 = new ArraySet<>(); + allowed0.add(ComponentName.unflattenFromString("a/a")); + approvedComponentsByUser.put(0, allowed0); + ArraySet<ComponentName> allowed10 = new ArraySet<>(); + allowed10.add(ComponentName.unflattenFromString("b/b")); + approvedComponentsByUser.put(10, allowed10); + + int nonProfileUser = 99; + ArraySet<ComponentName> allowedForNonProfileUser = new ArraySet<>(); + allowedForNonProfileUser.add(ComponentName.unflattenFromString("c/c")); + approvedComponentsByUser.put(nonProfileUser, allowedForNonProfileUser); + + IntArray users = new IntArray(); + users.add(nonProfileUser); + users.add(10); + users.add(0); + + SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>(); + spyOn(service.mUmInternal); + when(service.mUmInternal.isVisibleBackgroundFullUser(nonProfileUser)).thenReturn(true); + + service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser); + + assertTrue(service.isComponentEnabledForUser( + ComponentName.unflattenFromString("a/a"), 0)); + assertTrue(service.isComponentEnabledForPackage("a", 0)); + assertTrue(service.isComponentEnabledForUser( + ComponentName.unflattenFromString("b/b"), 10)); + assertTrue(service.isComponentEnabledForPackage("b", 0)); + assertTrue(service.isComponentEnabledForPackage("b", 10)); + assertTrue(service.isComponentEnabledForUser( + ComponentName.unflattenFromString("c/c"), nonProfileUser)); + assertTrue(service.isComponentEnabledForPackage("c", nonProfileUser)); + } + + + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testRebindService_profileUser() throws Exception { + final int profileUserId = 10; + when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true); + spyOn(mService); + ArgumentCaptor<IntArray> captor = ArgumentCaptor.forClass( + IntArray.class); + when(mService.allowRebindForParentUser()).thenReturn(true); + + mService.rebindServices(false, profileUserId); + + verify(mService).populateComponentsToBind(any(), captor.capture(), any()); + assertTrue(captor.getValue().contains(0)); + assertTrue(captor.getValue().contains(profileUserId)); + } + + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testRebindService_nonProfileUser() throws Exception { + final int userId = 99; + when(mUserProfiles.isProfileUser(userId, mContext)).thenReturn(false); + spyOn(mService); + ArgumentCaptor<IntArray> captor = ArgumentCaptor.forClass( + IntArray.class); + when(mService.allowRebindForParentUser()).thenReturn(true); + + mService.rebindServices(false, userId); + + verify(mService).populateComponentsToBind(any(), captor.capture(), any()); + assertFalse(captor.getValue().contains(0)); + assertTrue(captor.getValue().contains(userId)); + } + + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testRebindService_userAll() throws Exception { + final int userId = 99; + spyOn(mService); + spyOn(mService.mUmInternal); + when(mService.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(true); + ArgumentCaptor<IntArray> captor = ArgumentCaptor.forClass( + IntArray.class); + when(mService.allowRebindForParentUser()).thenReturn(true); + + mService.rebindServices(false, USER_ALL); + + verify(mService).populateComponentsToBind(any(), captor.capture(), any()); + assertTrue(captor.getValue().contains(0)); + assertTrue(captor.getValue().contains(userId)); + } + + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testOnUserStoppedWithVisibleBackgroundUser() throws Exception { + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + spyOn(service); + int userId = 99; + SparseArray<ArraySet<ComponentName>> approvedComponentsByUser = new SparseArray<>(); + ArraySet<ComponentName> allowedForNonProfileUser = new ArraySet<>(); + allowedForNonProfileUser.add(ComponentName.unflattenFromString("a/a")); + approvedComponentsByUser.put(userId, allowedForNonProfileUser); + IntArray users = new IntArray(); + users.add(userId); + SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>(); + spyOn(service.mUmInternal); + when(service.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(true); + service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser); + assertTrue(service.isComponentEnabledForUser( + ComponentName.unflattenFromString("a/a"), userId)); + assertTrue(service.isComponentEnabledForPackage("a", userId)); + + service.onUserStopped(userId); + + assertFalse(service.isComponentEnabledForUser( + ComponentName.unflattenFromString("a/a"), userId)); + assertFalse(service.isComponentEnabledForPackage("a", userId)); + verify(service).unbindUserServices(eq(userId)); + } + + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testUnbindServicesImpl_serviceOfForegroundUser() throws Exception { + int switchingUserId = 10; + int userId = 99; + + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + spyOn(service); + spyOn(service.mUmInternal); + when(service.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(false); + + IInterface iInterface = mock(IInterface.class); + when(iInterface.asBinder()).thenReturn(mock(IBinder.class)); + + ManagedServices.ManagedServiceInfo serviceInfo = service.new ManagedServiceInfo( + iInterface, ComponentName.unflattenFromString("a/a"), userId, false, + mock(ServiceConnection.class), 26, 34); + + Set<ManagedServices.ManagedServiceInfo> removableBoundServices = new ArraySet<>(); + removableBoundServices.add(serviceInfo); + + when(service.getRemovableConnectedServices()).thenReturn(removableBoundServices); + ArgumentCaptor<SparseArray<Set<ComponentName>>> captor = ArgumentCaptor.forClass( + SparseArray.class); + + service.unbindServicesImpl(switchingUserId, true); + + verify(service).unbindFromServices(captor.capture()); + + assertEquals(captor.getValue().size(), 1); + assertTrue(captor.getValue().indexOfKey(userId) != -1); + assertTrue(captor.getValue().get(userId).contains( + ComponentName.unflattenFromString("a/a"))); + } + + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testUnbindServicesImpl_serviceOfVisibleBackgroundUser() throws Exception { + int switchingUserId = 10; + int userId = 99; + + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + spyOn(service); + spyOn(service.mUmInternal); + when(service.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(true); + + IInterface iInterface = mock(IInterface.class); + when(iInterface.asBinder()).thenReturn(mock(IBinder.class)); + + ManagedServices.ManagedServiceInfo serviceInfo = service.new ManagedServiceInfo( + iInterface, ComponentName.unflattenFromString("a/a"), userId, + false, mock(ServiceConnection.class), 26, 34); + + Set<ManagedServices.ManagedServiceInfo> removableBoundServices = new ArraySet<>(); + removableBoundServices.add(serviceInfo); + + when(service.getRemovableConnectedServices()).thenReturn(removableBoundServices); + ArgumentCaptor<SparseArray<Set<ComponentName>>> captor = ArgumentCaptor.forClass( + SparseArray.class); + + service.unbindServicesImpl(switchingUserId, true); + + verify(service).unbindFromServices(captor.capture()); + + assertEquals(captor.getValue().size(), 0); + } + @Test public void testOnNullBinding() throws Exception { Context context = mock(Context.class); @@ -1681,6 +2051,7 @@ public class ManagedServicesTest extends UiServiceTestCase { assertFalse(service.isBound(cn, mZero.id)); assertFalse(service.isBound(cn, mTen.id)); } + @Test public void testOnPackagesChanged_nullValuesPassed_noNullPointers() { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { @@ -2012,6 +2383,7 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isComponentEnabledForCurrentProfiles_isThreadSafe() throws InterruptedException { for (UserInfo userInfo : mUm.getUsers()) { mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", userInfo.id, true); @@ -2024,6 +2396,20 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isComponentEnabledForUser_isThreadSafe() throws InterruptedException { + for (UserInfo userInfo : mUm.getUsers()) { + mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", userInfo.id, true); + } + testThreadSafety(() -> { + mService.rebindServices(false, 0); + assertThat(mService.isComponentEnabledForUser( + new ComponentName("pkg1", "cmp1"), 0)).isTrue(); + }, 20, 30); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isComponentEnabledForCurrentProfiles_profileUserId() { final int profileUserId = 10; when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true); @@ -2037,6 +2423,24 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isComponentEnabledForUser_profileUserId() { + final int profileUserId = 10; + when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true); + spyOn(mService); + doReturn(USER_CURRENT).when(mService).resolveUserId(anyInt()); + + // Only approve for parent user (0) + mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", 0, true); + + // Test that the component is enabled after calling rebindServices with profile userId (10) + mService.rebindServices(false, profileUserId); + assertThat(mService.isComponentEnabledForUser( + new ComponentName("pkg1", "cmp1"), profileUserId)).isTrue(); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isComponentEnabledForCurrentProfiles_profileUserId_NAS() { final int profileUserId = 10; when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true); @@ -2054,6 +2458,25 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isComponentEnabledForUser_profileUserId_NAS() { + final int profileUserId = 10; + when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true); + // Do not rebind for parent users (NAS use-case) + ManagedServices service = spy(mService); + when(service.allowRebindForParentUser()).thenReturn(false); + doReturn(USER_CURRENT).when(service).resolveUserId(anyInt()); + + // Only approve for parent user (0) + service.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", 0, true); + + // Test that the component is disabled after calling rebindServices with profile userId (10) + service.rebindServices(false, profileUserId); + assertThat(service.isComponentEnabledForUser( + new ComponentName("pkg1", "cmp1"), profileUserId)).isFalse(); + } + + @Test @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR) public void testManagedServiceInfoIsSystemUi() { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, @@ -2069,6 +2492,48 @@ public class ManagedServicesTest extends UiServiceTestCase { assertThat(service0.isSystemUi()).isFalse(); } + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testUserMatchesAndEnabled_profileUser() throws Exception { + int currentUserId = 10; + int profileUserId = 11; + + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + ManagedServices.ManagedServiceInfo listener = spy(service.new ManagedServiceInfo( + mock(IInterface.class), ComponentName.unflattenFromString("a/a"), currentUserId, + false, mock(ServiceConnection.class), 26, 34)); + + doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(profileUserId); + doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(currentUserId); + doReturn(true).when(listener).isEnabledForUser(); + doReturn(true).when(mUserProfiles).isCurrentProfile(anyInt()); + + assertThat(listener.enabledAndUserMatches(profileUserId)).isTrue(); + } + + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void testUserMatchesAndDisabled_visibleBackgroudUser() throws Exception { + int currentUserId = 10; + int profileUserId = 11; + int visibleBackgroundUserId = 12; + + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + ManagedServices.ManagedServiceInfo listener = spy(service.new ManagedServiceInfo( + mock(IInterface.class), ComponentName.unflattenFromString("a/a"), profileUserId, + false, mock(ServiceConnection.class), 26, 34)); + + doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(profileUserId); + doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(currentUserId); + doReturn(visibleBackgroundUserId).when(service.mUmInternal) + .getProfileParentId(visibleBackgroundUserId); + doReturn(true).when(listener).isEnabledForUser(); + + assertThat(listener.enabledAndUserMatches(visibleBackgroundUserId)).isFalse(); + } + private void mockServiceInfoWithMetaData(List<ComponentName> componentNames, ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas) throws RemoteException { @@ -2247,26 +2712,47 @@ public class ManagedServicesTest extends UiServiceTestCase { private void verifyExpectedBoundEntries(ManagedServices service, boolean primary) throws Exception { + verifyExpectedBoundEntries(service, primary, UserHandle.USER_CURRENT); + } + + private void verifyExpectedBoundEntries(ManagedServices service, boolean primary, + int targetUserId) throws Exception { ArrayMap<Integer, String> verifyMap = primary ? mExpectedPrimary.get(service.mApprovalLevel) : mExpectedSecondary.get(service.mApprovalLevel); for (int userId : verifyMap.keySet()) { for (String packageOrComponent : verifyMap.get(userId).split(":")) { if (!TextUtils.isEmpty(packageOrComponent)) { if (service.mApprovalLevel == APPROVAL_BY_PACKAGE) { - assertTrue(packageOrComponent, - service.isComponentEnabledForPackage(packageOrComponent)); + if (managedServicesConcurrentMultiuser()) { + assertTrue(packageOrComponent, + service.isComponentEnabledForPackage(packageOrComponent, + targetUserId)); + } else { + assertTrue(packageOrComponent, + service.isComponentEnabledForPackage(packageOrComponent)); + } for (int i = 1; i <= 3; i++) { ComponentName componentName = ComponentName.unflattenFromString( packageOrComponent +"/C" + i); - assertTrue(service.isComponentEnabledForCurrentProfiles( - componentName)); + if (managedServicesConcurrentMultiuser()) { + assertTrue(service.isComponentEnabledForUser( + componentName, targetUserId)); + } else { + assertTrue(service.isComponentEnabledForCurrentProfiles( + componentName)); + } verify(mIpm, times(1)).getServiceInfo( eq(componentName), anyLong(), anyInt()); } } else { ComponentName componentName = ComponentName.unflattenFromString(packageOrComponent); - assertTrue(service.isComponentEnabledForCurrentProfiles(componentName)); + if (managedServicesConcurrentMultiuser()) { + assertTrue(service.isComponentEnabledForUser(componentName, + targetUserId)); + } else { + assertTrue(service.isComponentEnabledForCurrentProfiles(componentName)); + } verify(mIpm, times(1)).getServiceInfo( eq(componentName), anyLong(), anyInt()); } 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 415e3accfa39..37ab541f12da 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -140,6 +140,7 @@ import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER; import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL; +import static com.android.server.notification.Flags.FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER; import static com.android.server.notification.Flags.FLAG_REJECT_OLD_NOTIFICATIONS; import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY; import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION; @@ -867,7 +868,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) { mPackageIntentReceiver = broadcastReceivers.get(i); } - if (filter.hasAction(Intent.ACTION_USER_SWITCHED) + if (filter.hasAction(Intent.ACTION_USER_STOPPED) + || filter.hasAction(Intent.ACTION_USER_SWITCHED) || filter.hasAction(Intent.ACTION_PROFILE_UNAVAILABLE) || filter.hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) { // There may be multiple receivers, get the NMS one @@ -11028,7 +11030,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void testAddAutomaticZenRule_typeManagedCanBeUsedByDeviceOwners() throws Exception { ZenModeHelper zenModeHelper = setUpMockZenTest(); mService.setCallerIsNormalPackage(); @@ -11046,20 +11047,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void testAddAutomaticZenRule_typeManagedCanBeUsedBySystem() throws Exception { addAutomaticZenRule_restrictedRuleTypeCanBeUsedBySystem(AutomaticZenRule.TYPE_MANAGED); } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void testAddAutomaticZenRule_typeManagedCannotBeUsedByRegularApps() throws Exception { addAutomaticZenRule_restrictedRuleTypeCannotBeUsedByRegularApps( AutomaticZenRule.TYPE_MANAGED); } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void testAddAutomaticZenRule_typeBedtimeCanBeUsedByWellbeing() throws Exception { ZenModeHelper zenModeHelper = setUpMockZenTest(); mService.setCallerIsNormalPackage(); @@ -11082,7 +11080,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void testAddAutomaticZenRule_typeBedtimeCanBeUsedBySystem() throws Exception { reset(mPackageManagerInternal); when(mPackageManagerInternal.isSameApp(eq(mPkg), eq(mUid), anyInt())).thenReturn(true); @@ -11090,7 +11087,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void testAddAutomaticZenRule_typeBedtimeCannotBeUsedByRegularApps() throws Exception { reset(mPackageManagerInternal); when(mPackageManagerInternal.isSameApp(eq(mPkg), eq(mUid), anyInt())).thenReturn(true); @@ -11133,7 +11129,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void addAutomaticZenRule_fromUser_mappedToOriginUser() throws Exception { ZenModeHelper zenModeHelper = setUpMockZenTest(); mService.isSystemUid = true; @@ -11145,7 +11140,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void addAutomaticZenRule_fromSystemNotUser_mappedToOriginSystem() throws Exception { ZenModeHelper zenModeHelper = setUpMockZenTest(); mService.isSystemUid = true; @@ -11157,7 +11151,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void addAutomaticZenRule_fromApp_mappedToOriginApp() throws Exception { ZenModeHelper zenModeHelper = setUpMockZenTest(); mService.setCallerIsNormalPackage(); @@ -11169,7 +11162,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void addAutomaticZenRule_fromAppFromUser_blocked() throws Exception { setUpMockZenTest(); mService.setCallerIsNormalPackage(); @@ -11179,7 +11171,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void updateAutomaticZenRule_fromUserFromSystem_allowed() throws Exception { ZenModeHelper zenModeHelper = setUpMockZenTest(); mService.isSystemUid = true; @@ -11191,7 +11182,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void updateAutomaticZenRule_fromUserFromApp_blocked() throws Exception { setUpMockZenTest(); mService.setCallerIsNormalPackage(); @@ -11201,7 +11191,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void removeAutomaticZenRule_fromUserFromSystem_allowed() throws Exception { ZenModeHelper zenModeHelper = setUpMockZenTest(); mService.isSystemUid = true; @@ -11213,7 +11202,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void removeAutomaticZenRule_fromUserFromApp_blocked() throws Exception { setUpMockZenTest(); mService.setCallerIsNormalPackage(); @@ -11223,7 +11211,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void setAutomaticZenRuleState_fromAppWithConditionFromUser_originUserInApp() throws Exception { ZenModeHelper zenModeHelper = setUpMockZenTest(); @@ -11238,7 +11225,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void setAutomaticZenRuleState_fromAppWithConditionNotFromUser_originApp() throws Exception { ZenModeHelper zenModeHelper = setUpMockZenTest(); @@ -11253,7 +11239,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void setAutomaticZenRuleState_fromSystemWithConditionFromUser_originUserInSystemUi() throws Exception { ZenModeHelper zenModeHelper = setUpMockZenTest(); @@ -11267,7 +11252,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyInt()); } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void setAutomaticZenRuleState_fromSystemWithConditionNotFromUser_originSystem() throws Exception { ZenModeHelper zenModeHelper = setUpMockZenTest(); @@ -11438,7 +11422,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void onAutomaticRuleStatusChanged_sendsBroadcastToRuleOwner() throws Exception { mService.mZenModeHelper.getCallbacks().forEach(c -> c.onAutomaticRuleStatusChanged( mUserId, "rule.owner.pkg", "rule_id", AUTOMATIC_RULE_STATUS_ACTIVATED)); @@ -16302,7 +16285,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { InOrder inOrder = inOrder(mPreferencesHelper, mService.mZenModeHelper); inOrder.verify(mService.mZenModeHelper).onUserSwitched(eq(20)); - inOrder.verify(mPreferencesHelper).syncChannelsBypassingDnd(); + inOrder.verify(mPreferencesHelper).syncHasPriorityChannels(); inOrder.verifyNoMoreInteractions(); } @@ -16318,11 +16301,25 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { InOrder inOrder = inOrder(mPreferencesHelper, mService.mZenModeHelper); inOrder.verify(mService.mZenModeHelper).onUserSwitched(eq(20)); - inOrder.verify(mPreferencesHelper).syncChannelsBypassingDnd(); + inOrder.verify(mPreferencesHelper).syncHasPriorityChannels(); inOrder.verifyNoMoreInteractions(); } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void onUserStopped_callBackToListeners() { + Intent intent = new Intent(Intent.ACTION_USER_STOPPED); + intent.putExtra(Intent.EXTRA_USER_HANDLE, 20); + + mUserIntentReceiver.onReceive(mContext, intent); + + verify(mConditionProviders).onUserStopped(eq(20)); + verify(mListeners).onUserStopped(eq(20)); + verify(mAssistants).onUserStopped(eq(20)); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isNotificationPolicyAccessGranted_invalidPackage() throws Exception { final String notReal = "NOT REAL"; final var checker = mService.permissionChecker; @@ -16339,6 +16336,25 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isNotificationPolicyAccessGranted_invalidPackage_concurrent_multiUser() + throws Exception { + final String notReal = "NOT REAL"; + final var checker = mService.permissionChecker; + + when(mPackageManagerClient.getPackageUidAsUser(eq(notReal), anyInt())).thenThrow( + PackageManager.NameNotFoundException.class); + + assertThat(mBinderService.isNotificationPolicyAccessGranted(notReal)).isFalse(); + verify(mPackageManagerClient).getPackageUidAsUser(eq(notReal), anyInt()); + verify(checker, never()).check(any(), anyInt(), anyInt(), anyBoolean()); + verify(mConditionProviders, never()).isPackageOrComponentAllowed(eq(notReal), anyInt()); + verify(mListeners, never()).isComponentEnabledForPackage(any(), anyInt()); + verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt()); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isNotificationPolicyAccessGranted_hasPermission() throws Exception { final String packageName = "target"; final int uid = 123; @@ -16357,6 +16373,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isNotificationPolicyAccessGranted_hasPermission_concurrent_multiUser() + throws Exception { + final String packageName = "target"; + final int uid = 123; + final var checker = mService.permissionChecker; + + when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid); + when(checker.check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + + assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue(); + verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt()); + verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true); + verify(mConditionProviders, never()).isPackageOrComponentAllowed(eq(packageName), anyInt()); + verify(mListeners, never()).isComponentEnabledForPackage(any(), anyInt()); + verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt()); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isNotificationPolicyAccessGranted_isPackageAllowed() throws Exception { final String packageName = "target"; final int uid = 123; @@ -16375,6 +16412,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isNotificationPolicyAccessGranted_isPackageAllowed_concurrent_multiUser() + throws Exception { + final String packageName = "target"; + final int uid = 123; + final var checker = mService.permissionChecker; + + when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid); + when(mConditionProviders.isPackageOrComponentAllowed(eq(packageName), anyInt())) + .thenReturn(true); + + assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue(); + verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt()); + verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true); + verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt()); + verify(mListeners, never()).isComponentEnabledForPackage(any(), anyInt()); + verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt()); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isNotificationPolicyAccessGranted_isComponentEnabled() throws Exception { final String packageName = "target"; final int uid = 123; @@ -16392,6 +16450,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isNotificationPolicyAccessGranted_isComponentEnabled_concurrent_multiUser() + throws Exception { + final String packageName = "target"; + final int uid = 123; + final var checker = mService.permissionChecker; + + when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid); + when(mListeners.isComponentEnabledForPackage(packageName, mUserId)).thenReturn(true); + + assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue(); + verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt()); + verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true); + verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt()); + verify(mListeners).isComponentEnabledForPackage(packageName, mUserId); + verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt()); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isNotificationPolicyAccessGranted_isDeviceOwner() throws Exception { final String packageName = "target"; final int uid = 123; @@ -16408,10 +16486,30 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mDevicePolicyManager).isActiveDeviceOwner(uid); } + @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isNotificationPolicyAccessGranted_isDeviceOwner_concurrent_multiUser() + throws Exception { + final String packageName = "target"; + final int uid = 123; + final var checker = mService.permissionChecker; + + when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid); + when(mDevicePolicyManager.isActiveDeviceOwner(uid)).thenReturn(true); + + assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue(); + verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt()); + verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true); + verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt()); + verify(mListeners).isComponentEnabledForPackage(packageName, mUserId); + verify(mDevicePolicyManager).isActiveDeviceOwner(uid); + } + /** * b/292163859 */ @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isNotificationPolicyAccessGranted_callerIsDeviceOwner() throws Exception { final String packageName = "target"; final int uid = 123; @@ -16430,7 +16528,32 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mDevicePolicyManager, never()).isActiveDeviceOwner(callingUid); } + /** + * b/292163859 + */ @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isNotificationPolicyAccessGranted_callerIsDeviceOwner_concurrent_multiUser() + throws Exception { + final String packageName = "target"; + final int uid = 123; + final int callingUid = Binder.getCallingUid(); + final var checker = mService.permissionChecker; + + when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid); + when(mDevicePolicyManager.isActiveDeviceOwner(callingUid)).thenReturn(true); + + assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isFalse(); + verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt()); + verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true); + verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt()); + verify(mListeners).isComponentEnabledForPackage(packageName, mUserId); + verify(mDevicePolicyManager).isActiveDeviceOwner(uid); + verify(mDevicePolicyManager, never()).isActiveDeviceOwner(callingUid); + } + + @Test + @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) public void isNotificationPolicyAccessGranted_notGranted() throws Exception { final String packageName = "target"; final int uid = 123; @@ -16447,6 +16570,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER) + public void isNotificationPolicyAccessGranted_notGranted_concurrent_multiUser() + throws Exception { + final String packageName = "target"; + final int uid = 123; + final var checker = mService.permissionChecker; + + when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid); + + assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isFalse(); + verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt()); + verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true); + verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt()); + verify(mListeners).isComponentEnabledForPackage(packageName, mUserId); + verify(mDevicePolicyManager).isActiveDeviceOwner(uid); + } + + @Test public void testResetDefaultDnd() { TestableNotificationManagerService service = spy(mService); UserInfo user = new UserInfo(0, "owner", 0); @@ -16481,7 +16622,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void setDeviceEffectsApplier_succeeds() throws Exception { initNMS(SystemService.PHASE_SYSTEM_SERVICES_READY); @@ -16492,7 +16632,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void setDeviceEffectsApplier_tooLate_throws() throws Exception { initNMS(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START); @@ -16501,7 +16640,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) public void setDeviceEffectsApplier_calledTwice_throws() throws Exception { initNMS(SystemService.PHASE_SYSTEM_SERVICES_READY); @@ -16513,7 +16651,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) public void setNotificationPolicy_mappedToImplicitRule() throws RemoteException { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mService.setCallerIsNormalPackage(); ZenModeHelper zenHelper = mock(ZenModeHelper.class); mService.mZenModeHelper = zenHelper; @@ -16530,7 +16667,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) public void setNotificationPolicy_systemCaller_setsGlobalPolicy() throws RemoteException { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); ZenModeHelper zenModeHelper = mock(ZenModeHelper.class); mService.mZenModeHelper = zenModeHelper; when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) @@ -16570,7 +16706,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private void setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy( @AssociationRequest.DeviceProfile String deviceProfile, boolean canSetGlobalPolicy) throws RemoteException { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mService.setCallerIsNormalPackage(); ZenModeHelper zenModeHelper = mock(ZenModeHelper.class); mService.mZenModeHelper = zenModeHelper; @@ -16597,7 +16732,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @DisableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) public void setNotificationPolicy_withoutCompat_setsGlobalPolicy() throws RemoteException { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mService.setCallerIsNormalPackage(); ZenModeHelper zenModeHelper = mock(ZenModeHelper.class); mService.mZenModeHelper = zenModeHelper; @@ -16613,7 +16747,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) public void getNotificationPolicy_mappedFromImplicitRule() throws RemoteException { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mService.setCallerIsNormalPackage(); ZenModeHelper zenHelper = mock(ZenModeHelper.class); mService.mZenModeHelper = zenHelper; @@ -16628,7 +16761,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) public void setInterruptionFilter_mappedToImplicitRule() throws RemoteException { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mService.setCallerIsNormalPackage(); ZenModeHelper zenHelper = mock(ZenModeHelper.class); mService.mZenModeHelper = zenHelper; @@ -16644,7 +16776,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) public void setInterruptionFilter_systemCaller_setsGlobalPolicy() throws RemoteException { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mService.setCallerIsNormalPackage(); ZenModeHelper zenModeHelper = mock(ZenModeHelper.class); mService.mZenModeHelper = zenModeHelper; @@ -16683,7 +16814,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private void setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen( @AssociationRequest.DeviceProfile String deviceProfile, boolean canSetGlobalPolicy) throws RemoteException { - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); ZenModeHelper zenModeHelper = mock(ZenModeHelper.class); mService.mZenModeHelper = zenModeHelper; mService.setCallerIsNormalPackage(); @@ -16708,7 +16838,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) public void requestInterruptionFilterFromListener_fromApp_doesNotSetGlobalZen() throws Exception { @@ -16726,7 +16855,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) public void requestInterruptionFilterFromListener_fromSystem_setsGlobalZen() throws Exception { @@ -16745,24 +16873,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @DisableFlags(android.app.Flags.FLAG_MODES_API) - public void requestInterruptionFilterFromListener_flagOff_callsRequestFromListener() - throws Exception { - mService.setCallerIsNormalPackage(); - mService.mZenModeHelper = mock(ZenModeHelper.class); - ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class); - when(mListeners.checkServiceTokenLocked(any())).thenReturn(info); - info.component = new ComponentName("pkg", "cls"); - - mBinderService.requestInterruptionFilterFromListener(mock(INotificationListener.class), - INTERRUPTION_FILTER_PRIORITY); - - verify(mService.mZenModeHelper).requestFromListener(eq(info.component), - eq(INTERRUPTION_FILTER_PRIORITY), eq(mUid), /* fromSystemOrSystemUi= */ eq(false)); - } - - @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) public void updateAutomaticZenRule_implicitRuleWithoutCPS_disallowedFromApp() throws Exception { setUpRealZenTest(); @@ -16788,7 +16898,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_API) @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) public void updateAutomaticZenRule_implicitRuleWithoutCPS_allowedFromSystem() throws Exception { setUpRealZenTest(); @@ -16814,7 +16923,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI}) + @EnableFlags(android.app.Flags.FLAG_MODES_UI) public void setNotificationPolicy_fromSystemApp_appliesPriorityChannelsAllowed() throws Exception { setUpRealZenTest(); @@ -16844,7 +16953,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI}) + @EnableFlags(android.app.Flags.FLAG_MODES_UI) @DisableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) public void setNotificationPolicy_fromRegularAppThatCanModifyPolicy_ignoresState() throws Exception { 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 640de174ba20..67e85ff2445d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -363,7 +363,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { .when(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI)); mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, - NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); + NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS, 0); when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy); when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString(), eq(null), anyString())).thenReturn(MODE_DEFAULT); @@ -2733,7 +2733,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW); mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false, uid, false); - assertFalse(mHelper.areChannelsBypassingDnd()); + assertFalse(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean()); } else { @@ -2748,7 +2748,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel2.setBypassDnd(true); mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); - assertTrue(mHelper.areChannelsBypassingDnd()); + assertTrue(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT), eq(true)); @@ -2760,7 +2760,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { // delete channels mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false); - assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND + assertTrue(mHelper.hasPriorityChannels()); // channel2 can still bypass DND if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean()); } else { @@ -2770,7 +2770,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { resetZenModeHelper(); mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false); - assertFalse(mHelper.areChannelsBypassingDnd()); + assertFalse(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT), eq(false)); @@ -2792,7 +2792,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW); mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false, uid, false); - assertFalse(mHelper.areChannelsBypassingDnd()); + assertFalse(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean()); } else { @@ -2807,7 +2807,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, update, true, true, uid, false); - assertTrue(mHelper.areChannelsBypassingDnd()); + assertTrue(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT), eq(true)); @@ -2829,7 +2829,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW); mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false, uid, false); - assertFalse(mHelper.areChannelsBypassingDnd()); + assertFalse(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean()); } else { @@ -2844,7 +2844,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel2.setBypassDnd(true); mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); - assertTrue(mHelper.areChannelsBypassingDnd()); + assertTrue(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT), eq(true)); @@ -2856,7 +2856,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { // delete channels mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false); - assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND + assertTrue(mHelper.hasPriorityChannels()); // channel2 can still bypass DND if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean()); } else { @@ -2866,7 +2866,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { resetZenModeHelper(); mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false); - assertFalse(mHelper.areChannelsBypassingDnd()); + assertFalse(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT), eq(false)); @@ -2884,9 +2884,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { // start in a 'allowed to bypass dnd state' mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, - NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); + NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS, 0); when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy); - mHelper.syncChannelsBypassingDnd(); + mHelper.syncHasPriorityChannels(); // create notification channel that can bypass dnd, but app is blocked // expected result: areChannelsBypassingDnd = false @@ -2899,7 +2899,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel2.setBypassDnd(true); mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); - assertFalse(mHelper.areChannelsBypassingDnd()); + assertFalse(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT), eq(false)); @@ -2917,9 +2917,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { // start in a 'allowed to bypass dnd state' mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, - NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); + NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS, 0); when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy); - mHelper.syncChannelsBypassingDnd(); + mHelper.syncHasPriorityChannels(); // create notification channel that can bypass dnd, but app is blocked // expected result: areChannelsBypassingDnd = false @@ -2927,7 +2927,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel2.setBypassDnd(true); mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); - assertFalse(mHelper.areChannelsBypassingDnd()); + assertFalse(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT), eq(false)); @@ -2945,9 +2945,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { // start in a 'allowed to bypass dnd state' mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, - NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); + NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS, 0); when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy); - mHelper.syncChannelsBypassingDnd(); + mHelper.syncHasPriorityChannels(); // create notification channel that can bypass dnd, but app is blocked // expected result: areChannelsBypassingDnd = false @@ -2955,7 +2955,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel2.setBypassDnd(true); mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); - assertFalse(mHelper.areChannelsBypassingDnd()); + assertFalse(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT), eq(false)); @@ -2977,7 +2977,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW); mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false, uid, false); - assertFalse(mHelper.areChannelsBypassingDnd()); + assertFalse(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean()); } else { @@ -2990,7 +2990,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { // expected result: areChannelsBypassingDnd = true channel.setBypassDnd(true); mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true); - assertTrue(mHelper.areChannelsBypassingDnd()); + assertTrue(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT), eq(true)); @@ -3004,7 +3004,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { // expected result: areChannelsBypassingDnd = false channel.setBypassDnd(false); mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true); - assertFalse(mHelper.areChannelsBypassingDnd()); + assertFalse(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT), eq(false)); @@ -3020,10 +3020,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { // start notification policy off with mAreChannelsBypassingDnd = true, but // RankingHelper should change to false mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, - NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); + NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS, 0); when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy); - mHelper.syncChannelsBypassingDnd(); - assertFalse(mHelper.areChannelsBypassingDnd()); + mHelper.syncHasPriorityChannels(); + assertFalse(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT), eq(false)); @@ -3039,7 +3039,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { // start notification policy off with mAreChannelsBypassingDnd = false mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0); when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy); - assertFalse(mHelper.areChannelsBypassingDnd()); + assertFalse(mHelper.hasPriorityChannels()); if (android.app.Flags.modesUi()) { verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean()); } else { @@ -3050,7 +3050,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void syncChannelsBypassingDnd_includesProfilesOfCurrentUser() throws Exception { + public void syncHasPriorityChannels_includesProfilesOfCurrentUser() throws Exception { when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0, 10})); when(mPermissionHelper.hasPermission(anyInt())).thenReturn(true); ApplicationInfo appInfo = new ApplicationInfo(); @@ -3067,13 +3067,13 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel("com.example", UserHandle.getUid(10, 444), withBypass, false, false, Process.SYSTEM_UID, true); - mHelper.syncChannelsBypassingDnd(); + mHelper.syncHasPriorityChannels(); - assertThat(mHelper.areChannelsBypassingDnd()).isTrue(); + assertThat(mHelper.hasPriorityChannels()).isTrue(); } @Test - public void syncChannelsBypassingDnd_excludesOtherUsers() throws Exception { + public void syncHasPriorityChannels_excludesOtherUsers() throws Exception { when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0})); when(mPermissionHelper.hasPermission(anyInt())).thenReturn(true); ApplicationInfo appInfo = new ApplicationInfo(); @@ -3090,9 +3090,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel("com.example", UserHandle.getUid(10, 444), withBypass, false, false, Process.SYSTEM_UID, true); - mHelper.syncChannelsBypassingDnd(); + mHelper.syncHasPriorityChannels(); - assertThat(mHelper.areChannelsBypassingDnd()).isFalse(); + assertThat(mHelper.hasPriorityChannels()).isFalse(); } @Test @@ -4495,6 +4495,27 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testBubblePreference_sameVersionWithSAWPermission() throws Exception { + when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(), + anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED); + + final String xml = "<ranking version=\"4\">\n" + + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\">\n" + + "<channel id=\"someId\" name=\"hi\"" + + " importance=\"3\"/>" + + "</package>" + + "</ranking>"; + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), + null); + parser.nextTag(); + mHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertEquals(BUBBLE_PREFERENCE_ALL, mHelper.getBubblePreference(PKG_O, UID_O)); + assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O)); + } + + @Test public void testBubblePreference_upgradeWithSAWThenUserOverride() throws Exception { when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(), anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java index f90034614383..ec428d506e7b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java @@ -17,9 +17,10 @@ package com.android.server.notification; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_LOW; - import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; + import static com.google.common.truth.Truth.assertThat; + import static junit.framework.TestCase.assertEquals; import static org.junit.Assert.assertTrue; @@ -29,7 +30,6 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import android.app.Flags; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -40,7 +40,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; -import android.media.AudioAttributes; import android.net.Uri; import android.os.Build; import android.os.UserHandle; @@ -67,7 +66,6 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Collections; -import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) @@ -154,7 +152,7 @@ public class RankingHelperTest extends UiServiceTestCase { .thenReturn(SOUND_URI); mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, - NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); + NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS, 0); when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy); mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper, mUsageStats, new String[] {ImportanceExtractor.class.getName()}, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java index 75552bc433c5..f3813437a9c5 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java @@ -20,10 +20,7 @@ import static android.service.notification.ZenAdapters.notificationPolicyToZenPo import static com.google.common.truth.Truth.assertThat; -import android.app.Flags; import android.app.NotificationManager.Policy; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.ZenPolicy; @@ -137,8 +134,7 @@ public class ZenAdaptersTest extends UiServiceTestCase { } @Test - @EnableFlags(Flags.FLAG_MODES_API) - public void notificationPolicyToZenPolicy_modesApi_priorityChannels() { + public void notificationPolicyToZenPolicy_priorityChannels() { Policy policy = new Policy(0, 0, 0, 0, Policy.policyState(false, true), 0); @@ -151,20 +147,4 @@ public class ZenAdaptersTest extends UiServiceTestCase { assertThat(zenPolicyNotAllowed.getPriorityChannelsAllowed()).isEqualTo( ZenPolicy.STATE_DISALLOW); } - - @Test - @DisableFlags(Flags.FLAG_MODES_API) - public void notificationPolicyToZenPolicy_noModesApi_priorityChannelsUnset() { - Policy policy = new Policy(0, 0, 0, 0, - Policy.policyState(false, true), 0); - - ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy); - assertThat(zenPolicy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET); - - Policy notAllowed = new Policy(0, 0, 0, 0, - Policy.policyState(false, false), 0); - ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed); - assertThat(zenPolicyNotAllowed.getPriorityChannelsAllowed()).isEqualTo( - ZenPolicy.STATE_UNSET); - } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java index af911e811e5e..9a2b748a9bcc 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java @@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; -import android.app.Flags; import android.os.Parcel; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.ZenDeviceEffects; @@ -31,7 +30,6 @@ import com.android.server.UiServiceTestCase; import com.google.common.collect.ImmutableSet; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,11 +40,6 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); - @Before - public final void setUp() { - mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); - } - @Test public void builder() { ZenDeviceEffects deviceEffects = diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index b42a6a5a7382..67efb9e76692 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -174,7 +174,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { } assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config)); - config.areChannelsBypassingDnd = true; + config.hasPriorityChannels = true; assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config)); if (Flags.modesUi()) { @@ -187,7 +187,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertFalse(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config)); - config.areChannelsBypassingDnd = false; + config.hasPriorityChannels = false; if (Flags.modesUi()) { config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy) .allowPriorityChannels(false) @@ -417,7 +417,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config)); assertTrue(ZenModeConfig.areAllZenBehaviorSoundsMuted(config)); - config.areChannelsBypassingDnd = true; + config.hasPriorityChannels = true; if (Flags.modesUi()) { config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy) .allowPriorityChannels(true) @@ -429,7 +429,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertFalse(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config)); assertFalse(ZenModeConfig.areAllZenBehaviorSoundsMuted(config)); - config.areChannelsBypassingDnd = false; + config.hasPriorityChannels = false; if (Flags.modesUi()) { config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy) .allowPriorityChannels(false) @@ -488,33 +488,33 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.zenPolicy = null; rule.zenDeviceEffects = null; - assertThat(rule.canBeUpdatedByApp()).isTrue(); + assertThat(rule.isUserModified()).isFalse(); rule.userModifiedFields = 1; - assertThat(rule.canBeUpdatedByApp()).isFalse(); + assertThat(rule.isUserModified()).isTrue(); } @Test public void testCanBeUpdatedByApp_policyModified() throws Exception { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.zenPolicy = new ZenPolicy(); - assertThat(rule.canBeUpdatedByApp()).isTrue(); + assertThat(rule.isUserModified()).isFalse(); rule.zenPolicyUserModifiedFields = 1; - assertThat(rule.canBeUpdatedByApp()).isFalse(); + assertThat(rule.isUserModified()).isTrue(); } @Test public void testCanBeUpdatedByApp_deviceEffectsModified() throws Exception { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.zenDeviceEffects = new ZenDeviceEffects.Builder().build(); - assertThat(rule.canBeUpdatedByApp()).isTrue(); + assertThat(rule.isUserModified()).isFalse(); rule.zenDeviceEffectsUserModifiedFields = 1; - assertThat(rule.canBeUpdatedByApp()).isFalse(); + assertThat(rule.isUserModified()).isTrue(); } @Test @@ -548,7 +548,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.creationTime = 123; rule.id = "id"; rule.zenMode = INTERRUPTION_FILTER; - rule.modified = true; rule.name = NAME; rule.setConditionOverride(OVERRIDE_DEACTIVATE); rule.pkg = OWNER.getPackageName(); @@ -564,6 +563,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); if (Flags.modesUi()) { rule.disabledOrigin = ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI; + if (Flags.modesCleanupImplicit()) { + rule.lastActivation = Instant.ofEpochMilli(456); + } } config.automaticRules.put(rule.id, rule); @@ -585,7 +587,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.condition, ruleActual.condition); assertEquals(rule.enabled, ruleActual.enabled); assertEquals(rule.creationTime, ruleActual.creationTime); - assertEquals(rule.modified, ruleActual.modified); assertEquals(rule.conditionId, ruleActual.conditionId); assertEquals(rule.name, ruleActual.name); assertEquals(rule.zenMode, ruleActual.zenMode); @@ -602,6 +603,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.deletionInstant, ruleActual.deletionInstant); if (Flags.modesUi()) { assertEquals(rule.disabledOrigin, ruleActual.disabledOrigin); + if (Flags.modesCleanupImplicit()) { + assertEquals(rule.lastActivation, ruleActual.lastActivation); + } } if (Flags.backupRestoreLogging()) { verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_RULES, 2); @@ -620,7 +624,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.creationTime = 123; rule.id = "id"; rule.zenMode = INTERRUPTION_FILTER; - rule.modified = true; rule.name = NAME; rule.setConditionOverride(OVERRIDE_DEACTIVATE); rule.pkg = OWNER.getPackageName(); @@ -636,6 +639,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); if (Flags.modesUi()) { rule.disabledOrigin = ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI; + if (Flags.modesCleanupImplicit()) { + rule.lastActivation = Instant.ofEpochMilli(789); + } } Parcel parcel = Parcel.obtain(); @@ -651,7 +657,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.condition, parceled.condition); assertEquals(rule.enabled, parceled.enabled); assertEquals(rule.creationTime, parceled.creationTime); - assertEquals(rule.modified, parceled.modified); assertEquals(rule.conditionId, parceled.conditionId); assertEquals(rule.name, parceled.name); assertEquals(rule.zenMode, parceled.zenMode); @@ -668,6 +673,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.deletionInstant, parceled.deletionInstant); if (Flags.modesUi()) { assertEquals(rule.disabledOrigin, parceled.disabledOrigin); + if (Flags.modesCleanupImplicit()) { + assertEquals(rule.lastActivation, parceled.lastActivation); + } } assertEquals(rule, parceled); @@ -685,7 +693,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.creationTime = 123; rule.id = "id"; rule.zenMode = Settings.Global.ZEN_MODE_ALARMS; - rule.modified = true; rule.name = "name"; rule.snoozing = true; rule.pkg = "b"; @@ -705,7 +712,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.condition, fromXml.condition); assertEquals(rule.enabled, fromXml.enabled); assertEquals(rule.creationTime, fromXml.creationTime); - assertEquals(rule.modified, fromXml.modified); assertEquals(rule.conditionId, fromXml.conditionId); assertEquals(rule.name, fromXml.name); assertEquals(rule.zenMode, fromXml.zenMode); @@ -721,7 +727,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.enabled = ENABLED; rule.id = "id"; rule.zenMode = INTERRUPTION_FILTER; - rule.modified = true; rule.name = NAME; rule.setConditionOverride(OVERRIDE_DEACTIVATE); rule.pkg = OWNER.getPackageName(); @@ -753,6 +758,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); if (Flags.modesUi()) { rule.disabledOrigin = ZenModeConfig.ORIGIN_APP; + if (Flags.modesCleanupImplicit()) { + rule.lastActivation = Instant.ofEpochMilli(123); + } } ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -770,7 +778,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.condition, fromXml.condition); assertEquals(rule.enabled, fromXml.enabled); assertEquals(rule.creationTime, fromXml.creationTime); - assertEquals(rule.modified, fromXml.modified); assertEquals(rule.conditionId, fromXml.conditionId); assertEquals(rule.name, fromXml.name); assertEquals(rule.zenMode, fromXml.zenMode); @@ -789,6 +796,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.deletionInstant, fromXml.deletionInstant); if (Flags.modesUi()) { assertEquals(rule.disabledOrigin, fromXml.disabledOrigin); + if (Flags.modesCleanupImplicit()) { + assertEquals(rule.lastActivation, fromXml.lastActivation); + } } } @@ -916,7 +926,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME; assertThat(rule.userModifiedFields).isEqualTo(1); - assertThat(rule.canBeUpdatedByApp()).isFalse(); + assertThat(rule.isUserModified()).isTrue(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeRuleXml(rule, baos); @@ -924,7 +934,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule fromXml = readRuleXml(bais); assertThat(fromXml.userModifiedFields).isEqualTo(rule.userModifiedFields); - assertThat(fromXml.canBeUpdatedByApp()).isFalse(); + assertThat(fromXml.isUserModified()).isTrue(); } @Test @@ -1259,7 +1269,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.creationTime = 123; rule.id = "id"; rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; - rule.modified = true; rule.name = "name"; rule.pkg = "b"; config.automaticRules.put("key", rule); @@ -1348,7 +1357,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { config.setSuppressedVisualEffects(0); config.setAllowPriorityChannels(false); } - config.areChannelsBypassingDnd = false; + config.hasPriorityChannels = false; return config; } @@ -1383,7 +1392,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { config.setSuppressedVisualEffects(0); config.setAllowPriorityChannels(true); } - config.areChannelsBypassingDnd = false; + config.hasPriorityChannels = false; return config; } @@ -1410,7 +1419,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { config.setAllowConversationsFrom(CONVERSATION_SENDERS_NONE); config.setSuppressedVisualEffects(0); } - config.areChannelsBypassingDnd = false; + config.hasPriorityChannels = false; return config; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java index b138c72875a6..6d0bf8b322fd 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java @@ -16,7 +16,6 @@ package com.android.server.notification; -import static android.app.Flags.FLAG_MODES_API; import static android.app.Flags.FLAG_MODES_UI; import static com.google.common.truth.Truth.assertThat; @@ -64,7 +63,6 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; @@ -78,20 +76,14 @@ public class ZenModeDiffTest extends UiServiceTestCase { // version is not included in the diff; manual & automatic rules have special handling; // deleted rules are not included in the diff. public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS = - android.app.Flags.modesApi() - ? Set.of("version", "manualRule", "automaticRules", "deletedRules") - : Set.of("version", "manualRule", "automaticRules"); - - // allowPriorityChannels is flagged by android.app.modes_api - public static final Set<String> ZEN_MODE_CONFIG_FLAGGED_FIELDS = - Set.of("allowPriorityChannels"); + Set.of("version", "manualRule", "automaticRules", "deletedRules"); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { - return FlagsParameterization.progressionOf(FLAG_MODES_API, FLAG_MODES_UI); + return FlagsParameterization.progressionOf(FLAG_MODES_UI); } public ZenModeDiffTest(FlagsParameterization flags) { @@ -147,7 +139,7 @@ public class ZenModeDiffTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void testRuleDiff_toStringNoChangeAddRemove() throws Exception { // Start with two identical rules ZenModeConfig.ZenRule r1 = makeRule(); @@ -164,7 +156,7 @@ public class ZenModeDiffTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void testRuleDiff_toString() throws Exception { // Start with two identical rules ZenModeConfig.ZenRule r1 = makeRule(); @@ -218,7 +210,6 @@ public class ZenModeDiffTest extends UiServiceTestCase { + "mPriorityCalls:2->1, " + "mConversationSenders:2->1, " + "mAllowChannels:2->1}, " - + "modified:true->false, " + "pkg:string1->string2, " + "zenDeviceEffects:ZenDeviceEffectsDiff{" + "mGrayscale:true->false, " @@ -241,7 +232,7 @@ public class ZenModeDiffTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void testRuleDiff_toStringNullStartPolicy() throws Exception { // Start with two identical rules ZenModeConfig.ZenRule r1 = makeRule(); @@ -278,7 +269,6 @@ public class ZenModeDiffTest extends UiServiceTestCase { + "creationTime:200->100, " + "enabler:string1->string2, " + "zenPolicy:ZenPolicyDiff{added}, " - + "modified:true->false, " + "pkg:string1->string2, " + "zenDeviceEffects:ZenDeviceEffectsDiff{added}, " + "triggerDescription:string1->string2, " @@ -485,16 +475,10 @@ public class ZenModeDiffTest extends UiServiceTestCase { // "Metadata" fields are never compared. Set<String> exemptFields = new LinkedHashSet<>( Set.of("userModifiedFields", "zenPolicyUserModifiedFields", - "zenDeviceEffectsUserModifiedFields", "deletionInstant", "disabledOrigin")); + "zenDeviceEffectsUserModifiedFields", "deletionInstant", "disabledOrigin", + "lastActivation")); // Flagged fields are only compared if their flag is on. - if (!Flags.modesApi()) { - exemptFields.addAll( - Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION, - RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL, - RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, - RuleDiff.FIELD_LEGACY_SUPPRESSED_EFFECTS)); - } - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { exemptFields.add(RuleDiff.FIELD_SNOOZING); // Obsolete. } else { exemptFields.add(RuleDiff.FIELD_CONDITION_OVERRIDE); @@ -530,35 +514,6 @@ public class ZenModeDiffTest extends UiServiceTestCase { ArrayMap<String, Object> expectedFrom = new ArrayMap<>(); ArrayMap<String, Object> expectedTo = new ArrayMap<>(); List<Field> fieldsForDiff = getFieldsForDiffCheck( - ZenModeConfig.class, getConfigExemptAndFlaggedFields(), false); - generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo); - - ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2); - assertTrue(d.hasDiff()); - - // Now diff them and check that each of the fields has a diff - for (Field f : fieldsForDiff) { - String name = f.getName(); - assertNotNull("diff not found for field: " + name, d.getDiffForField(name)); - assertTrue(d.getDiffForField(name).hasDiff()); - assertTrue("unexpected field: " + name, expectedFrom.containsKey(name)); - assertTrue("unexpected field: " + name, expectedTo.containsKey(name)); - assertEquals(expectedFrom.get(name), d.getDiffForField(name).from()); - assertEquals(expectedTo.get(name), d.getDiffForField(name).to()); - } - } - - @Test - @EnableFlags(FLAG_MODES_API) - public void testConfigDiff_fieldDiffs_flagOn() throws Exception { - // these two start the same - ZenModeConfig c1 = new ZenModeConfig(); - ZenModeConfig c2 = new ZenModeConfig(); - - // maps mapping field name -> expected output value as we set diffs - ArrayMap<String, Object> expectedFrom = new ArrayMap<>(); - ArrayMap<String, Object> expectedTo = new ArrayMap<>(); - List<Field> fieldsForDiff = getFieldsForDiffCheck( ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS, false); generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo); @@ -656,14 +611,6 @@ public class ZenModeDiffTest extends UiServiceTestCase { assertEquals("different", automaticDiffs.get("ruleId").getDiffForField("pkg").to()); } - // Helper method that merges the base exempt fields with fields that are flagged - private Set getConfigExemptAndFlaggedFields() { - Set merged = new HashSet(); - merged.addAll(ZEN_MODE_CONFIG_EXEMPT_FIELDS); - merged.addAll(ZEN_MODE_CONFIG_FLAGGED_FIELDS); - return merged; - } - // Helper methods for working with configs, policies, rules // Just makes a zen rule with fields filled in private ZenModeConfig.ZenRule makeRule() { @@ -676,20 +623,17 @@ public class ZenModeDiffTest extends UiServiceTestCase { rule.creationTime = 123; rule.id = "ruleId"; rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; - rule.modified = false; rule.name = "name"; rule.setConditionOverride(ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE); rule.pkg = "a"; - if (android.app.Flags.modesApi()) { - rule.allowManualInvocation = true; - rule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME; - rule.iconResName = "res"; - rule.triggerDescription = "At night"; - rule.zenDeviceEffects = new ZenDeviceEffects.Builder() - .setShouldDimWallpaper(true) - .build(); - rule.userModifiedFields = AutomaticZenRule.FIELD_NAME; - } + rule.allowManualInvocation = true; + rule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME; + rule.iconResName = "res"; + rule.triggerDescription = "At night"; + rule.zenDeviceEffects = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .build(); + rule.userModifiedFields = AutomaticZenRule.FIELD_NAME; return rule; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java index a49f5a89b11b..2f0b3ecb593a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java @@ -37,7 +37,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import android.app.Flags; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager.Policy; @@ -492,8 +491,6 @@ public class ZenModeFilteringTest extends UiServiceTestCase { @Test public void testAllowChannels_priorityPackage() { - mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); - // Notification with package priority = PRIORITY_MAX (assigned to indicate canBypassDnd) NotificationRecord r = getNotificationRecord(); r.setPackagePriority(Notification.PRIORITY_MAX); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 31b9cf72584c..0ab11e0cbe3d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -23,7 +23,7 @@ import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; import static android.app.AutomaticZenRule.TYPE_THEATER; import static android.app.AutomaticZenRule.TYPE_UNKNOWN; import static android.app.Flags.FLAG_BACKUP_RESTORE_LOGGING; -import static android.app.Flags.FLAG_MODES_API; +import static android.app.Flags.FLAG_MODES_CLEANUP_IMPLICIT; import static android.app.Flags.FLAG_MODES_MULTIUSER; import static android.app.Flags.FLAG_MODES_UI; import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED; @@ -85,6 +85,7 @@ import static android.service.notification.ZenPolicy.VISUAL_EFFECT_LIGHTS; import static android.service.notification.ZenPolicy.VISUAL_EFFECT_PEEK; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS; +import static com.android.os.dnd.DNDProtoEnums.CONV_IMPORTANT; import static com.android.os.dnd.DNDProtoEnums.PEOPLE_STARRED; import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG; import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW; @@ -124,7 +125,10 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; +import static java.time.temporal.ChronoUnit.DAYS; + import android.Manifest; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.AlarmManager; @@ -219,11 +223,11 @@ import java.io.Reader; import java.io.StringWriter; import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Calendar; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -713,7 +717,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testTotalSilence_consolidatedPolicyDisallowsAll() { // Start with zen mode off just to make sure global/manual mode isn't doing anything. mZenModeHelper.mZenMode = ZEN_MODE_OFF; @@ -746,7 +749,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testAlarmsOnly_consolidatedPolicyOnlyAllowsAlarmsAndMedia() { // Start with zen mode off just to make sure global/manual mode isn't doing anything. mZenModeHelper.mZenMode = ZEN_MODE_OFF; @@ -1136,8 +1138,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testProto() throws InvalidProtocolBufferException { mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, - Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, null, - "test", CUSTOM_PKG_UID); + ORIGIN_USER_IN_SYSTEMUI, null, "test", CUSTOM_PKG_UID); mZenModeHelper.mConfig.automaticRules = new ArrayMap<>(); // no automatic rules @@ -1262,7 +1263,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testProtoWithAutoRuleCustomPolicy() throws Exception { setupZenConfig(); // clear any automatic rules just to make sure @@ -1304,7 +1304,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testProtoWithAutoRuleWithModifiedFields() throws Exception { setupZenConfig(); mZenModeHelper.mConfig.automaticRules = new ArrayMap<>(); @@ -2005,7 +2004,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testReadXml_onModesApi_noUpgrade() throws Exception { // When reading XML for something that is already on the modes API system, make sure no // rules' policies get changed. @@ -2053,7 +2051,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testReadXml_upgradeToModesApi_makesCustomPolicies() throws Exception { // When reading in an XML file written from a pre-modes-API version, confirm that we create // a custom policy matching the global config for any automatic rule with no specified @@ -2105,7 +2102,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testReadXml_upgradeToModesApi_fillsInCustomPolicies() throws Exception { // When reading in an XML file written from a pre-modes-API version, confirm that for an // underspecified ZenPolicy, we fill in all of the gaps with things from the global config @@ -2165,7 +2161,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testReadXml_upgradeToModesApi_existingDefaultRulesGetCustomPolicy() throws Exception { setupZenConfig(); @@ -2227,7 +2222,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void testReadXml_upgradeToModesUi_resetsImplicitRuleIcon() throws Exception { setupZenConfig(); mZenModeHelper.mConfig.automaticRules.clear(); @@ -2241,13 +2236,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mConfig.automaticRules.put(implicitRuleBeforeModesUi.id, implicitRuleBeforeModesUi); // Plus one other normal rule. - ZenRule anotherRule = newZenRule("other_pkg", Instant.now(), null); - anotherRule.id = "other_rule"; + ZenRule anotherRule = newZenRule("other_rule", "other_pkg", Instant.now()); anotherRule.iconResName = "other_icon"; anotherRule.type = TYPE_IMMERSIVE; mZenModeHelper.mConfig.automaticRules.put(anotherRule.id, anotherRule); - // Write with pre-modes-ui = (modes_api) version, then re-read. + // Write with pre-modes-ui version, then re-read. ByteArrayOutputStream baos = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_API); TypedXmlPullParser parser = Xml.newFastPullParser(); parser.setInput(new BufferedInputStream( @@ -2265,7 +2259,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void testReadXml_onModesUi_implicitRulesUntouched() throws Exception { setupZenConfig(); mZenModeHelper.mConfig.automaticRules.clear(); @@ -2279,8 +2273,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { implicitRuleWithModesUi); // Plus one other normal rule. - ZenRule anotherRule = newZenRule("other_pkg", Instant.now(), null); - anotherRule.id = "other_rule"; + ZenRule anotherRule = newZenRule("other_rule", "other_pkg", Instant.now()); anotherRule.iconResName = "other_icon"; anotherRule.type = TYPE_IMMERSIVE; mZenModeHelper.mConfig.automaticRules.put(anotherRule.id, anotherRule); @@ -2342,7 +2335,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // shouldn't update rule that's been modified ZenModeConfig.ZenRule updatedDefaultRule = new ZenModeConfig.ZenRule(); - updatedDefaultRule.modified = true; + updatedDefaultRule.userModifiedFields = AutomaticZenRule.FIELD_NAME; updatedDefaultRule.enabled = false; updatedDefaultRule.creationTime = 0; updatedDefaultRule.id = SCHEDULE_DEFAULT_RULE_ID; @@ -2370,8 +2363,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { // will update rule that is not enabled and modified ZenModeConfig.ZenRule customDefaultRule = new ZenModeConfig.ZenRule(); customDefaultRule.pkg = SystemZenRules.PACKAGE_ANDROID; + customDefaultRule.userModifiedFields = AutomaticZenRule.FIELD_NAME; customDefaultRule.enabled = false; - customDefaultRule.modified = false; customDefaultRule.creationTime = 0; customDefaultRule.id = SCHEDULE_DEFAULT_RULE_ID; customDefaultRule.name = "Schedule Default Rule"; @@ -2391,7 +2384,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.ZenRule ruleAfterUpdating = mZenModeHelper.mConfig.automaticRules.get(SCHEDULE_DEFAULT_RULE_ID); assertEquals(customDefaultRule.enabled, ruleAfterUpdating.enabled); - assertEquals(customDefaultRule.modified, ruleAfterUpdating.modified); + assertEquals(customDefaultRule.userModifiedFields, ruleAfterUpdating.userModifiedFields); assertEquals(customDefaultRule.id, ruleAfterUpdating.id); assertEquals(customDefaultRule.conditionId, ruleAfterUpdating.conditionId); assertNotEquals(defaultRuleName, ruleAfterUpdating.name); // update name @@ -2401,8 +2394,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) - public void testDefaultRulesFromConfig_modesApi_getPolicies() { + public void testDefaultRulesFromConfig_getPolicies() { // After mZenModeHelper was created, set some things in the policy so it's changed from // default. setupZenConfig(); @@ -2530,7 +2522,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); assertTrue(ruleInConfig != null); assertEquals(zenRule.isEnabled(), ruleInConfig.enabled); - assertEquals(zenRule.isModified(), ruleInConfig.modified); assertEquals(zenRule.getConditionId(), ruleInConfig.conditionId); assertEquals(NotificationManager.zenModeFromInterruptionFilter( zenRule.getInterruptionFilter(), -1), ruleInConfig.zenMode); @@ -2551,7 +2542,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); assertTrue(ruleInConfig != null); assertEquals(zenRule.isEnabled(), ruleInConfig.enabled); - assertEquals(zenRule.isModified(), ruleInConfig.modified); assertEquals(zenRule.getConditionId(), ruleInConfig.conditionId); assertEquals(NotificationManager.zenModeFromInterruptionFilter( zenRule.getInterruptionFilter(), -1), ruleInConfig.zenMode); @@ -2560,8 +2550,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) - public void testAddAutomaticZenRule_modesApi_fillsInDefaultValues() { + public void testAddAutomaticZenRule_fillsInDefaultValues() { // When a new automatic zen rule is added with only some fields filled in, ensure that // all unset fields are filled in with device defaults. @@ -2762,7 +2751,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void addAutomaticZenRule_fromApp_ignoresHiddenEffects() { ZenDeviceEffects zde = new ZenDeviceEffects.Builder() @@ -2799,7 +2787,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void addAutomaticZenRule_fromSystem_respectsHiddenEffects() { ZenDeviceEffects zde = new ZenDeviceEffects.Builder() .setShouldDisplayGrayscale(true) @@ -2828,7 +2815,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void addAutomaticZenRule_fromUser_respectsHiddenEffects() throws Exception { ZenDeviceEffects zde = new ZenDeviceEffects.Builder() .setShouldDisplayGrayscale(true) @@ -2859,7 +2845,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void updateAutomaticZenRule_fromApp_preservesPreviousHiddenEffects() { ZenDeviceEffects original = new ZenDeviceEffects.Builder() .setShouldDisableTapToWake(true) @@ -2896,7 +2881,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void updateAutomaticZenRule_fromSystem_updatesHiddenEffects() { ZenDeviceEffects original = new ZenDeviceEffects.Builder() .setShouldDisableTapToWake(true) @@ -2925,7 +2909,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void updateAutomaticZenRule_fromUser_updatesHiddenEffects() { ZenDeviceEffects original = new ZenDeviceEffects.Builder() .setShouldDisableTapToWake(true) @@ -2958,7 +2941,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void updateAutomaticZenRule_nullPolicy_doesNothing() { // Test that when updateAutomaticZenRule is called with a null policy, nothing changes // about the existing policy. @@ -2985,7 +2967,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void updateAutomaticZenRule_overwritesExistingPolicy() { // Test that when updating an automatic zen rule with an existing policy, the newly set // fields overwrite those from the previous policy, but unset fields in the new policy @@ -3024,7 +3005,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test - @EnableFlags(FLAG_MODES_API) public void addAutomaticZenRule_withTypeBedtime_replacesDisabledSleeping() { ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS, ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID); @@ -3044,7 +3024,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void addAutomaticZenRule_withTypeBedtime_keepsEnabledSleeping() { ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS, ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID); @@ -3065,7 +3044,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void addAutomaticZenRule_withTypeBedtime_keepsCustomizedSleeping() { ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS, ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID); @@ -3086,7 +3064,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void updateAutomaticZenRule_withTypeBedtime_replacesDisabledSleeping() { ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS, ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID); @@ -3113,7 +3091,34 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) + public void getAutomaticZenRules_returnsOwnedRules() { + AutomaticZenRule myRule1 = new AutomaticZenRule.Builder("My Rule 1", Uri.parse("1")) + .setPackage(mPkg) + .setConfigurationActivity(new ComponentName(mPkg, "myActivity")) + .build(); + AutomaticZenRule myRule2 = new AutomaticZenRule.Builder("My Rule 2", Uri.parse("2")) + .setPackage(mPkg) + .setConfigurationActivity(new ComponentName(mPkg, "myActivity")) + .build(); + AutomaticZenRule otherPkgRule = new AutomaticZenRule.Builder("Other", Uri.parse("3")) + .setPackage("com.other.package") + .setConfigurationActivity(new ComponentName("com.other.package", "theirActivity")) + .build(); + + String rule1Id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, myRule1, + ORIGIN_APP, "reason", CUSTOM_PKG_UID); + String rule2Id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, myRule2, + ORIGIN_APP, "reason", CUSTOM_PKG_UID); + String otherRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, + "com.other.package", otherPkgRule, ORIGIN_APP, "reason", CUSTOM_PKG_UID); + + Map<String, AutomaticZenRule> rules = mZenModeHelper.getAutomaticZenRules( + UserHandle.CURRENT, CUSTOM_PKG_UID); + + assertThat(rules.keySet()).containsExactly(rule1Id, rule2Id); + } + + @Test public void testSetManualZenMode() { setupZenConfig(); @@ -3133,7 +3138,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) @DisableFlags(FLAG_MODES_UI) public void setManualZenMode_off_snoozesActiveRules() { for (ZenChangeOrigin origin : ZenChangeOrigin.values()) { @@ -3172,7 +3176,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setManualZenMode_off_doesNotSnoozeRulesIfFromUserInSystemUi() { for (ZenChangeOrigin origin : ZenChangeOrigin.values()) { // Start with an active rule and an inactive rule @@ -3246,7 +3250,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Turn zen mode on (to important_interruptions) // Need to additionally call the looper in order to finish the post-apply-config process mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, - Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "", null, SYSTEM_UID); + ORIGIN_USER_IN_SYSTEMUI, "", null, SYSTEM_UID); // Now turn zen mode off, but via a different package UID -- this should get registered as // "not an action by the user" because some other app is changing zen mode @@ -3273,14 +3277,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0)); assertEquals(DNDProtoEnums.MANUAL_RULE, mZenModeEventLogger.getChangedRuleType(0)); assertEquals(1, mZenModeEventLogger.getNumRulesActive(0)); - assertThat(mZenModeEventLogger.getFromSystemOrSystemUi(0)).isEqualTo( - !(Flags.modesUi() || Flags.modesApi())); + assertThat(mZenModeEventLogger.getFromSystemOrSystemUi(0)).isFalse(); assertTrue(mZenModeEventLogger.getIsUserAction(0)); assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(0)); checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0)); // change origin should be populated only under modes_ui assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo( - (Flags.modesApi() && Flags.modesUi()) ? ORIGIN_USER_IN_SYSTEMUI : 0); + (Flags.modesUi()) ? ORIGIN_USER_IN_SYSTEMUI : 0); // and from turning zen mode off: // - event ID: DND_TURNED_OFF @@ -3298,11 +3301,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(0, mZenModeEventLogger.getNumRulesActive(1)); assertFalse(mZenModeEventLogger.getIsUserAction(1)); assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1)); - if (Flags.modesApi()) { - assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull(); - } else { - checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1)); - } + assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull(); assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo( Flags.modesUi() ? ORIGIN_APP : 0); } @@ -3334,8 +3333,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Event 2: "User" turns off the automatic rule (sets it to not enabled) zenRule.setEnabled(false); mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, id, zenRule, - Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "", - SYSTEM_UID); + ORIGIN_USER_IN_SYSTEMUI, "", SYSTEM_UID); AutomaticZenRule systemRule = new AutomaticZenRule("systemRule", null, @@ -3345,8 +3343,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String systemId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(), systemRule, - Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "test", - SYSTEM_UID); + ORIGIN_USER_IN_SYSTEMUI, "test", SYSTEM_UID); // Event 3: turn on the system rule mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, systemId, @@ -3355,8 +3352,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Event 4: "User" deletes the rule mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, systemId, - Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "", - SYSTEM_UID); + ORIGIN_USER_IN_SYSTEMUI, "", SYSTEM_UID); // In total, this represents 4 events assertEquals(4, mZenModeEventLogger.numLoggedChanges()); @@ -3394,11 +3390,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertTrue(mZenModeEventLogger.getIsUserAction(1)); assertThat(mZenModeEventLogger.getPackageUid(1)).isEqualTo( Flags.modesUi() ? CUSTOM_PKG_UID : SYSTEM_UID); - if (Flags.modesApi()) { - assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull(); - } else { - checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1)); - } + assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull(); assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo( Flags.modesUi() ? ORIGIN_USER_IN_SYSTEMUI : 0); @@ -3426,7 +3418,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testZenModeEventLog_automaticRuleActivatedFromAppByAppAndUser() throws IllegalArgumentException { mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); @@ -3816,13 +3807,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Now change apps bypassing to true ZenModeConfig newConfig = mZenModeHelper.mConfig.copy(); - newConfig.areChannelsBypassingDnd = true; + newConfig.hasPriorityChannels = true; mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, newConfig.toNotificationPolicy(), ORIGIN_SYSTEM, SYSTEM_UID); assertEquals(2, mZenModeEventLogger.numLoggedChanges()); // and then back to false, all without changing anything else - newConfig.areChannelsBypassingDnd = false; + newConfig.hasPriorityChannels = false; mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, newConfig.toNotificationPolicy(), ORIGIN_SYSTEM, SYSTEM_UID); assertEquals(3, mZenModeEventLogger.numLoggedChanges()); @@ -3869,10 +3860,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testZenModeEventLog_policyAllowChannels() { - // when modes_api flag is on, ensure that any change in allow_channels gets logged, - // even when there are no other changes. + // Ensure that any change in allow_channels gets logged, even when there are no other + // changes. mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); // Default zen config has allow channels = priority (aka on) @@ -3919,7 +3909,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testZenModeEventLog_ruleWithInterruptionFilterAll_notLoggedAsDndChange() { mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); @@ -3961,7 +3950,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testZenModeEventLog_activeRuleTypes() { mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); @@ -4050,43 +4038,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @DisableFlags(FLAG_MODES_API) - public void testUpdateConsolidatedPolicy_preModesApiDefaultRulesOnly_takesGlobalDefault() { - setupZenConfig(); - // When there's one automatic rule active and it doesn't specify a policy, test that the - // resulting consolidated policy is one that matches the default rule settings. - AutomaticZenRule zenRule = new AutomaticZenRule("name", - null, - new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"), - ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), - null, - NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, - mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID); - - // enable the rule - mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, - new Condition(zenRule.getConditionId(), "", STATE_TRUE), - ORIGIN_SYSTEM, SYSTEM_UID); - - assertEquals(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT), - mZenModeHelper.getConsolidatedNotificationPolicy()); - - // inspect the consolidated policy. Based on setupZenConfig() values. - assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms()); - assertFalse(mZenModeHelper.mConsolidatedPolicy.allowMedia()); - assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem()); - assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders()); - assertTrue(mZenModeHelper.mConsolidatedPolicy.allowCalls()); - assertEquals(PRIORITY_SENDERS_STARRED, mZenModeHelper.mConsolidatedPolicy.allowCallsFrom()); - assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages()); - assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations()); - assertTrue(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()); - assertFalse(mZenModeHelper.mConsolidatedPolicy.showBadges()); - } - - @Test - public void testUpdateConsolidatedPolicy_modesApiDefaultRulesOnly_takesDefault() { + public void testUpdateConsolidatedPolicy_defaultRulesOnly_takesDefault() { setupZenConfig(); // When there's one automatic rule active and it doesn't specify a policy, test that the @@ -4113,53 +4065,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @DisableFlags(FLAG_MODES_API) - public void testUpdateConsolidatedPolicy_preModesApiCustomPolicyOnly_fillInWithGlobal() { - setupZenConfig(); - - // when there's only one automatic rule active and it has a custom policy, make sure that's - // what the consolidated policy reflects whether or not it's stricter than what the global - // config would specify. - ZenPolicy customPolicy = new ZenPolicy.Builder() - .allowAlarms(true) // more lenient than default - .allowMedia(true) // more lenient than default - .allowRepeatCallers(false) // more restrictive than default - .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // more restrictive than default - .showBadges(true) // more lenient - .showPeeking(false) // more restrictive - .build(); - - AutomaticZenRule zenRule = new AutomaticZenRule("name", - null, - new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"), - ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), - customPolicy, - NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, - mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID); - - // enable the rule; this will update the consolidated policy - mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, - new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID); - - // since this is the only active rule, the consolidated policy should match the custom - // policy for every field specified, and take default values (from device default or - // manual policy) for unspecified things - assertTrue(mZenModeHelper.mConsolidatedPolicy.allowAlarms()); // custom - assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMedia()); // custom - assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem()); // default - assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders()); // default - assertFalse(mZenModeHelper.mConsolidatedPolicy.allowCalls()); // custom - assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages()); // default - assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations()); // default - assertFalse(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()); // custom - assertTrue(mZenModeHelper.mConsolidatedPolicy.showBadges()); // custom - assertFalse(mZenModeHelper.mConsolidatedPolicy.showPeeking()); // custom - } - - @Test - @EnableFlags(FLAG_MODES_API) - public void testUpdateConsolidatedPolicy_modesApiCustomPolicyOnly_fillInWithDefault() { + public void testUpdateConsolidatedPolicy_customPolicyOnly_fillInWithDefault() { setupZenConfig(); // when there's only one automatic rule active and it has a custom policy, make sure that's @@ -4204,68 +4110,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @DisableFlags(FLAG_MODES_API) - public void testUpdateConsolidatedPolicy_preModesApiDefaultAndCustomActive_mergesWithGlobal() { - setupZenConfig(); - - // when there are two rules active, one inheriting the default policy and one setting its - // own custom policy, they should be merged to form the most restrictive combination. - - // rule 1: no custom policy - AutomaticZenRule zenRule = new AutomaticZenRule("name", - null, - new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"), - ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), - null, - NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, - mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID); - - // enable rule 1 - mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, - new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID); - - // custom policy for rule 2 - ZenPolicy customPolicy = new ZenPolicy.Builder() - .allowAlarms(true) // more lenient than default - .allowMedia(true) // more lenient than default - .allowRepeatCallers(false) // more restrictive than default - .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // more restrictive than default - .showBadges(true) // more lenient - .showPeeking(false) // more restrictive - .build(); - - AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", - null, - new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"), - ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), - customPolicy, - NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, - mContext.getPackageName(), zenRule2, ORIGIN_SYSTEM, "test", SYSTEM_UID); - - // enable rule 2; this will update the consolidated policy - mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2, - new Condition(zenRule2.getConditionId(), "", STATE_TRUE), - ORIGIN_SYSTEM, SYSTEM_UID); - - // now both rules should be on, and the consolidated policy should reflect the most - // restrictive option of each of the two - assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms()); // default stricter - assertFalse(mZenModeHelper.mConsolidatedPolicy.allowMedia()); // default stricter - assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem()); // default, unset in custom - assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders()); // default - assertFalse(mZenModeHelper.mConsolidatedPolicy.allowCalls()); // custom stricter - assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages()); // default, unset in custom - assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations()); // default - assertFalse(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()); // custom stricter - assertFalse(mZenModeHelper.mConsolidatedPolicy.showBadges()); // default stricter - assertFalse(mZenModeHelper.mConsolidatedPolicy.showPeeking()); // custom stricter - } - - @Test - @EnableFlags(FLAG_MODES_API) - public void testUpdateConsolidatedPolicy_modesApiDefaultAndCustomActive_mergesWithDefault() { + public void testUpdateConsolidatedPolicy_defaultAndCustomActive_mergesWithDefault() { setupZenConfig(); // when there are two rules active, one inheriting the default policy and one setting its @@ -4328,7 +4173,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testUpdateConsolidatedPolicy_allowChannels() { setupZenConfig(); @@ -4377,7 +4221,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testUpdateConsolidatedPolicy_ignoresActiveRulesWithInterruptionFilterAll() { setupZenConfig(); @@ -4428,7 +4271,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void zenRuleToAutomaticZenRule_allFields() { when(mPackageManager.getPackagesForUid(anyInt())).thenReturn( new String[]{OWNER.getPackageName()}); @@ -4442,7 +4284,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { rule.creationTime = 123; rule.id = "id"; rule.zenMode = INTERRUPTION_FILTER_ZR; - rule.modified = true; rule.name = NAME; rule.setConditionOverride(OVERRIDE_DEACTIVATE); rule.pkg = OWNER.getPackageName(); @@ -4473,7 +4314,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void automaticZenRuleToZenRule_allFields() { when(mPackageManager.getPackagesForUid(anyInt())).thenReturn( new String[]{OWNER.getPackageName()}); @@ -4515,7 +4355,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void updateAutomaticZenRule_fromApp_updatesNameUnlessUserModified() { // Add a starting rule with the name OriginalName. AutomaticZenRule azrBase = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID) @@ -4573,7 +4412,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void updateAutomaticZenRule_fromUser_updatesBitmaskAndValue() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) @@ -4627,7 +4465,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void updateAutomaticZenRule_fromSystemUi_updatesValues() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) @@ -4678,7 +4515,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void updateAutomaticZenRule_fromApp_updatesValuesIfRuleNotUserModified() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) @@ -4754,7 +4590,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void addAutomaticZenRule_updatesValues() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) @@ -4777,11 +4612,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(storedRule.canBeUpdatedByApp()).isTrue(); + assertThat(storedRule.isUserModified()).isFalse(); } @Test - @EnableFlags(FLAG_MODES_API) public void updateAutomaticZenRule_nullDeviceEffectsUpdate() { // Adds a starting rule with empty zen policies and device effects ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(); @@ -4809,7 +4643,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void updateAutomaticZenRule_nullPolicyUpdate() { // Adds a starting rule with set zen policy and empty device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) @@ -4838,7 +4671,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void automaticZenRuleToZenRule_nullToNonNullPolicyUpdate() { when(mContext.checkCallingPermission(anyString())) .thenReturn(PackageManager.PERMISSION_GRANTED); @@ -4888,7 +4720,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { STATE_DISALLOW); ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(storedRule.canBeUpdatedByApp()).isFalse(); + assertThat(storedRule.isUserModified()).isTrue(); assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo( ZenPolicy.FIELD_ALLOW_CHANNELS | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS @@ -4902,7 +4734,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void automaticZenRuleToZenRule_nullToNonNullDeviceEffectsUpdate() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) @@ -4931,7 +4762,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(storedRule.canBeUpdatedByApp()).isFalse(); + assertThat(storedRule.isUserModified()).isTrue(); assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo( ZenDeviceEffects.FIELD_GRAYSCALE); } @@ -5007,7 +4838,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testUpdateAutomaticRule_activated_triggersBroadcast() throws Exception { setupZenConfig(); @@ -5047,7 +4877,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testUpdateAutomaticRule_deactivatedByUser_triggersBroadcast() throws Exception { setupZenConfig(); @@ -5091,7 +4920,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testUpdateAutomaticRule_deactivatedByApp_triggersBroadcast() throws Exception { setupZenConfig(); @@ -5167,7 +4995,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void updateAutomaticZenRule_ruleChanged_deactivatesRule() { assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID) @@ -5191,7 +5018,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void updateAutomaticZenRule_ruleNotChanged_doesNotDeactivateRule() { assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID) @@ -5214,7 +5040,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void updateAutomaticZenRule_ruleChangedByUser_doesNotDeactivateRule() { assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID) @@ -5239,7 +5065,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void updateAutomaticZenRule_ruleChangedByUser_doesNotDeactivateRule_forWatch() { when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true); @@ -5266,7 +5091,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void updateAutomaticZenRule_ruleDisabledByUser_doesNotReactivateOnReenable() { assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID) @@ -5291,7 +5116,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void updateAutomaticZenRule_changeOwnerForSystemRule_allowed() { when(mContext.checkCallingPermission(anyString())) .thenReturn(PackageManager.PERMISSION_GRANTED); @@ -5314,7 +5139,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void updateAutomaticZenRule_changeOwnerForAppOwnedRule_ignored() { AutomaticZenRule original = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) .setOwner(new ComponentName(mContext.getPackageName(), "old.third.party.cps")) @@ -5335,7 +5160,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void removeAutomaticZenRule_propagatesOriginToEffectsApplier() { mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); reset(mDeviceEffectsApplier); @@ -5358,7 +5182,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testDeviceEffects_applied() { mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT)); @@ -5384,7 +5207,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testSettingDeviceEffects_throwsExceptionIfAlreadySet() { mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); @@ -5394,7 +5216,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testDeviceEffects_onDeactivateRule_applied() { mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); @@ -5413,7 +5234,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testDeviceEffects_changeToConsolidatedEffects_applied() { mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT)); @@ -5453,7 +5273,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() { mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT)); @@ -5478,7 +5297,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testDeviceEffects_activeBeforeApplierProvided_appliedWhenProvided() { ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(); String ruleId = addRuleWithEffects(zde); @@ -5494,7 +5312,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testDeviceEffects_onUserSwitch_appliedImmediately() { mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT)); @@ -5522,7 +5339,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING}) + @EnableFlags({FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING}) public void testDeviceEffects_allowsGrayscale() { mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); reset(mDeviceEffectsApplier); @@ -5539,7 +5356,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING}) + @EnableFlags({FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING}) public void testDeviceEffects_whileDriving_avoidsGrayscale() { mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); reset(mDeviceEffectsApplier); @@ -5563,7 +5380,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING}) + @EnableFlags({FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING}) public void testDeviceEffects_whileDrivingWithGrayscale_allowsGrayscale() { mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); reset(mDeviceEffectsApplier); @@ -5594,7 +5411,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void removeAndAddAutomaticZenRule_wasCustomized_isRestored() { // Start with a rule. mZenModeHelper.mConfig.automaticRules.clear(); @@ -5651,7 +5467,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void removeAndAddAutomaticZenRule_wasNotCustomized_isNotRestored() { // Start with a single rule. mZenModeHelper.mConfig.automaticRules.clear(); @@ -5685,7 +5500,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void removeAndAddAutomaticZenRule_recreatedButNotByApp_isNotRestored() { // Start with a single rule. mZenModeHelper.mConfig.automaticRules.clear(); @@ -5736,7 +5550,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void removeAndAddAutomaticZenRule_removedByUser_isNotRestored() { // Start with a single rule. mZenModeHelper.mConfig.automaticRules.clear(); @@ -5779,7 +5592,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void removeAndAddAutomaticZenRule_ifChangingComponent_isAllowedAndDoesNotRestore() { // Start with a rule. mZenModeHelper.mConfig.automaticRules.clear(); @@ -5824,7 +5637,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void removeAutomaticZenRule_preservedForRestoringByPackageAndConditionId() { mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS, PERMISSION_GRANTED); // So that canManageAZR passes although packages don't match. @@ -5874,7 +5686,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void removeAllZenRules_preservedForRestoring() { mZenModeHelper.mConfig.automaticRules.clear(); @@ -5898,14 +5709,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void removeAllZenRules_fromSystem_deletesPreservedRulesToo() { mZenModeHelper.mConfig.automaticRules.clear(); // Start with deleted rules from 2 different packages. Instant now = Instant.ofEpochMilli(1701796461000L); - ZenRule pkg1Rule = newZenRule("pkg1", now.minus(1, ChronoUnit.DAYS), now); - ZenRule pkg2Rule = newZenRule("pkg2", now.minus(2, ChronoUnit.DAYS), now); + ZenRule pkg1Rule = newDeletedZenRule("1", "pkg1", now.minus(1, DAYS), now); + ZenRule pkg2Rule = newDeletedZenRule("2", "pkg2", now.minus(2, DAYS), now); mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg1Rule), pkg1Rule); mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg2Rule), pkg2Rule); @@ -5918,7 +5728,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void removeAndAddAutomaticZenRule_wasActive_isRestoredAsInactive() { // Start with a rule. mZenModeHelper.mConfig.automaticRules.clear(); @@ -5968,7 +5777,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void removeAndAddAutomaticZenRule_wasSnoozed_isRestoredAsInactive() { // Start with a rule. mZenModeHelper.mConfig.automaticRules.clear(); @@ -6023,12 +5831,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testRuleCleanup() throws Exception { Instant now = Instant.ofEpochMilli(1701796461000L); - Instant yesterday = now.minus(1, ChronoUnit.DAYS); - Instant aWeekAgo = now.minus(7, ChronoUnit.DAYS); - Instant twoMonthsAgo = now.minus(60, ChronoUnit.DAYS); + Instant yesterday = now.minus(1, DAYS); + Instant aWeekAgo = now.minus(7, DAYS); + Instant twoMonthsAgo = now.minus(60, DAYS); mTestClock.setNowMillis(now.toEpochMilli()); when(mPackageManager.getPackageInfo(eq("good_pkg"), anyInt())) @@ -6041,24 +5848,28 @@ public class ZenModeHelperTest extends UiServiceTestCase { config.user = 42; mZenModeHelper.mConfigs.put(42, config); // okay rules (not deleted, package exists, with a range of creation dates). - config.automaticRules.put("ar1", newZenRule("good_pkg", now, null)); - config.automaticRules.put("ar2", newZenRule("good_pkg", yesterday, null)); - config.automaticRules.put("ar3", newZenRule("good_pkg", twoMonthsAgo, null)); + config.automaticRules.put("ar1", newZenRule("ar1", "good_pkg", now)); + config.automaticRules.put("ar2", newZenRule("ar2", "good_pkg", yesterday)); + config.automaticRules.put("ar3", newZenRule("ar3", "good_pkg", twoMonthsAgo)); // newish rules for a missing package - config.automaticRules.put("ar4", newZenRule("bad_pkg", yesterday, null)); + config.automaticRules.put("ar4", newZenRule("ar4", "bad_pkg", yesterday)); // oldish rules belonging to a missing package - config.automaticRules.put("ar5", newZenRule("bad_pkg", aWeekAgo, null)); + config.automaticRules.put("ar5", newZenRule("ar5", "bad_pkg", aWeekAgo)); // rules deleted recently - config.deletedRules.put("del1", newZenRule("good_pkg", twoMonthsAgo, yesterday)); - config.deletedRules.put("del2", newZenRule("good_pkg", twoMonthsAgo, aWeekAgo)); + config.deletedRules.put("del1", + newDeletedZenRule("del1", "good_pkg", twoMonthsAgo, yesterday)); + config.deletedRules.put("del2", + newDeletedZenRule("del2", "good_pkg", twoMonthsAgo, aWeekAgo)); // rules deleted a long time ago - config.deletedRules.put("del3", newZenRule("good_pkg", twoMonthsAgo, twoMonthsAgo)); + config.deletedRules.put("del3", + newDeletedZenRule("del3", "good_pkg", twoMonthsAgo, twoMonthsAgo)); // rules for a missing package, created recently and deleted recently - config.deletedRules.put("del4", newZenRule("bad_pkg", yesterday, now)); + config.deletedRules.put("del4", newDeletedZenRule("del4", "bad_pkg", yesterday, now)); // rules for a missing package, created a long time ago and deleted recently - config.deletedRules.put("del5", newZenRule("bad_pkg", twoMonthsAgo, now)); + config.deletedRules.put("del5", newDeletedZenRule("del5", "bad_pkg", twoMonthsAgo, now)); // rules for a missing package, created a long time ago and deleted a long time ago - config.deletedRules.put("del6", newZenRule("bad_pkg", twoMonthsAgo, twoMonthsAgo)); + config.deletedRules.put("del6", + newDeletedZenRule("del6", "bad_pkg", twoMonthsAgo, twoMonthsAgo)); mZenModeHelper.onUserSwitched(42); // copies config and cleans it up. @@ -6068,20 +5879,120 @@ public class ZenModeHelperTest extends UiServiceTestCase { .containsExactly("del1", "del2", "del4"); } - private static ZenRule newZenRule(String pkg, Instant createdAt, @Nullable Instant deletedAt) { + @Test + @EnableFlags({FLAG_MODES_UI, FLAG_MODES_CLEANUP_IMPLICIT}) + public void testRuleCleanup_removesNotRecentlyUsedNotModifiedImplicitRules() throws Exception { + Instant now = Instant.ofEpochMilli(1701796461000L); + Instant yesterday = now.minus(1, DAYS); + Instant aWeekAgo = now.minus(7, DAYS); + Instant twoMonthsAgo = now.minus(60, DAYS); + Instant aYearAgo = now.minus(365, DAYS); + mTestClock.setNowMillis(now.toEpochMilli()); + when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(new PackageInfo()); + + // Set up a config to be loaded, containing a bunch of implicit rules + ZenModeConfig config = new ZenModeConfig(); + config.user = 42; + mZenModeHelper.mConfigs.put(42, config); + // used recently + ZenRule usedRecently1 = newImplicitZenRule("pkg1", aYearAgo, yesterday); + ZenRule usedRecently2 = newImplicitZenRule("pkg2", aYearAgo, aWeekAgo); + config.automaticRules.put(usedRecently1.id, usedRecently1); + config.automaticRules.put(usedRecently2.id, usedRecently2); + // not used in a long time + ZenRule longUnused = newImplicitZenRule("pkg3", aYearAgo, twoMonthsAgo); + config.automaticRules.put(longUnused.id, longUnused); + // created a long time ago, before lastActivation tracking + ZenRule oldAndLastUsageUnknown = newImplicitZenRule("pkg4", twoMonthsAgo, null); + config.automaticRules.put(oldAndLastUsageUnknown.id, oldAndLastUsageUnknown); + // created a short time ago, before lastActivation tracking + ZenRule newAndLastUsageUnknown = newImplicitZenRule("pkg5", aWeekAgo, null); + config.automaticRules.put(newAndLastUsageUnknown.id, newAndLastUsageUnknown); + // not used in a long time, but was customized by user + ZenRule longUnusedButCustomized = newImplicitZenRule("pkg6", aYearAgo, twoMonthsAgo); + longUnusedButCustomized.zenPolicyUserModifiedFields = ZenPolicy.FIELD_CONVERSATIONS; + config.automaticRules.put(longUnusedButCustomized.id, longUnusedButCustomized); + // created a long time ago, before lastActivation tracking, and was customized by user + ZenRule oldAndLastUsageUnknownAndCustomized = newImplicitZenRule("pkg7", twoMonthsAgo, + null); + oldAndLastUsageUnknownAndCustomized.userModifiedFields = AutomaticZenRule.FIELD_ICON; + config.automaticRules.put(oldAndLastUsageUnknownAndCustomized.id, + oldAndLastUsageUnknownAndCustomized); + + mZenModeHelper.onUserSwitched(42); // copies config and cleans it up. + + // The recently used OR modified OR last-used-unknown rules stay. + assertThat(mZenModeHelper.mConfig.automaticRules.values()) + .comparingElementsUsing(IGNORE_METADATA) + .containsExactly(usedRecently1, usedRecently2, oldAndLastUsageUnknown, + newAndLastUsageUnknown, longUnusedButCustomized, + oldAndLastUsageUnknownAndCustomized); + } + + @Test + @EnableFlags({FLAG_MODES_UI, FLAG_MODES_CLEANUP_IMPLICIT}) + public void testRuleCleanup_assignsLastActivationToImplicitRules() throws Exception { + Instant now = Instant.ofEpochMilli(1701796461000L); + Instant aWeekAgo = now.minus(7, DAYS); + Instant aYearAgo = now.minus(365, DAYS); + mTestClock.setNowMillis(now.toEpochMilli()); + when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(new PackageInfo()); + + // Set up a config to be loaded, containing implicit rules. + ZenModeConfig config = new ZenModeConfig(); + config.user = 42; + mZenModeHelper.mConfigs.put(42, config); + // with last activation known + ZenRule usedRecently = newImplicitZenRule("pkg1", aYearAgo, aWeekAgo); + config.automaticRules.put(usedRecently.id, usedRecently); + // created a long time ago, with last activation unknown + ZenRule oldAndLastUsageUnknown = newImplicitZenRule("pkg4", aYearAgo, null); + config.automaticRules.put(oldAndLastUsageUnknown.id, oldAndLastUsageUnknown); + // created a short time ago, with last activation unknown + ZenRule newAndLastUsageUnknown = newImplicitZenRule("pkg5", aWeekAgo, null); + config.automaticRules.put(newAndLastUsageUnknown.id, newAndLastUsageUnknown); + + mZenModeHelper.onUserSwitched(42); // copies config and cleans it up. + + // All rules stayed. + usedRecently = getZenRule(usedRecently.id); + oldAndLastUsageUnknown = getZenRule(oldAndLastUsageUnknown.id); + newAndLastUsageUnknown = getZenRule(newAndLastUsageUnknown.id); + + // The rules with an unknown last usage have been assigned a placeholder one. + assertThat(usedRecently.lastActivation).isEqualTo(aWeekAgo); + assertThat(oldAndLastUsageUnknown.lastActivation).isEqualTo(now); + assertThat(newAndLastUsageUnknown.lastActivation).isEqualTo(now); + } + + private static ZenRule newDeletedZenRule(String id, String pkg, Instant createdAt, + @NonNull Instant deletedAt) { + ZenRule rule = newZenRule(id, pkg, createdAt); + rule.deletionInstant = deletedAt; + return rule; + } + + private static ZenRule newImplicitZenRule(String pkg, @NonNull Instant createdAt, + @Nullable Instant lastActivatedAt) { + ZenRule implicitRule = newZenRule(implicitRuleId(pkg), pkg, createdAt); + implicitRule.lastActivation = lastActivatedAt; + return implicitRule; + } + + private static ZenRule newZenRule(String id, String pkg, Instant createdAt) { ZenRule rule = new ZenRule(); + rule.id = id; rule.pkg = pkg; rule.creationTime = createdAt.toEpochMilli(); rule.enabled = true; - rule.deletionInstant = deletedAt; + rule.deletionInstant = null; // Plus stuff so that isValidAutomaticRule() passes - rule.name = "A rule from " + pkg + " created on " + createdAt; + rule.name = "Rule " + id; rule.conditionId = Uri.parse(rule.name); return rule; } @Test - @EnableFlags(FLAG_MODES_API) public void getAutomaticZenRuleState_ownedRule_returnsRuleState() { String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(), @@ -6112,14 +6023,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void getAutomaticZenRuleState_notOwnedRule_returnsStateUnknown() { // Assume existence of a system-owned rule that is currently ACTIVE. - ZenRule systemRule = newZenRule("android", Instant.now(), null); + ZenRule systemRule = newZenRule("systemRule", "android", Instant.now()); systemRule.zenMode = ZEN_MODE_ALARMS; systemRule.condition = new Condition(systemRule.conditionId, "on", Condition.STATE_TRUE); ZenModeConfig config = mZenModeHelper.mConfig.copy(); - config.automaticRules.put("systemRule", systemRule); + config.automaticRules.put(systemRule.id, systemRule); mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID); assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS); @@ -6128,15 +6038,14 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void setAutomaticZenRuleState_idForNotOwnedRule_ignored() { // Assume existence of an other-package-owned rule that is currently ACTIVE. assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); - ZenRule otherRule = newZenRule("another.package", Instant.now(), null); + ZenRule otherRule = newZenRule("otherRule", "another.package", Instant.now()); otherRule.zenMode = ZEN_MODE_ALARMS; otherRule.condition = new Condition(otherRule.conditionId, "on", Condition.STATE_TRUE); ZenModeConfig config = mZenModeHelper.mConfig.copy(); - config.automaticRules.put("otherRule", otherRule); + config.automaticRules.put(otherRule.id, otherRule); mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID); assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS); @@ -6149,15 +6058,14 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void setAutomaticZenRuleStateFromConditionProvider_conditionForNotOwnedRule_ignored() { // Assume existence of an other-package-owned rule that is currently ACTIVE. assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); - ZenRule otherRule = newZenRule("another.package", Instant.now(), null); + ZenRule otherRule = newZenRule("otherRule", "another.package", Instant.now()); otherRule.zenMode = ZEN_MODE_ALARMS; otherRule.condition = new Condition(otherRule.conditionId, "on", Condition.STATE_TRUE); ZenModeConfig config = mZenModeHelper.mConfig.copy(); - config.automaticRules.put("otherRule", otherRule); + config.automaticRules.put(otherRule.id, otherRule); mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID); assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS); @@ -6171,7 +6079,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testCallbacks_policy() throws Exception { setupZenConfig(); assertThat(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).allowReminders()) @@ -6193,7 +6100,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void testCallbacks_consolidatedPolicy() throws Exception { assertThat(mZenModeHelper.getConsolidatedNotificationPolicy().allowMedia()).isTrue(); SettableFuture<Policy> futureConsolidatedPolicy = SettableFuture.create(); @@ -6219,7 +6125,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void applyGlobalZenModeAsImplicitZenRule_createsImplicitRuleAndActivatesIt() { mZenModeHelper.mConfig.automaticRules.clear(); @@ -6235,7 +6140,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void applyGlobalZenModeAsImplicitZenRule_updatesImplicitRuleAndActivatesIt() { mZenModeHelper.mConfig.automaticRules.clear(); @@ -6257,7 +6161,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void applyGlobalZenModeAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() { mZenModeHelper.mConfig.automaticRules.clear(); String pkg = mContext.getPackageName(); @@ -6290,7 +6193,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void applyGlobalZenModeAsImplicitZenRule_ruleCustomizedButNotFilter_updatesRule() { mZenModeHelper.mConfig.automaticRules.clear(); String pkg = mContext.getPackageName(); @@ -6322,7 +6224,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void applyGlobalZenModeAsImplicitZenRule_modeOff_deactivatesImplicitRule() { mZenModeHelper.mConfig.automaticRules.clear(); mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, mPkg, CUSTOM_PKG_UID, @@ -6339,7 +6240,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void applyGlobalZenModeAsImplicitZenRule_modeOffButNoPreviousRule_ignored() { mZenModeHelper.mConfig.automaticRules.clear(); @@ -6350,7 +6250,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void applyGlobalZenModeAsImplicitZenRule_update_unsnoozesRule() { mZenModeHelper.mConfig.automaticRules.clear(); @@ -6375,7 +6274,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void applyGlobalZenModeAsImplicitZenRule_again_refreshesRuleName() { mZenModeHelper.mConfig.automaticRules.clear(); mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, @@ -6394,7 +6293,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void applyGlobalZenModeAsImplicitZenRule_again_doesNotChangeCustomizedRuleName() { mZenModeHelper.mConfig.automaticRules.clear(); mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, @@ -6420,19 +6319,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @DisableFlags(FLAG_MODES_API) - public void applyGlobalZenModeAsImplicitZenRule_flagOff_ignored() { - mZenModeHelper.mConfig.automaticRules.clear(); - - withoutWtfCrash( - () -> mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, - CUSTOM_PKG_NAME, CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS)); - - assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty(); - } - - @Test - @EnableFlags(FLAG_MODES_API) public void applyGlobalPolicyAsImplicitZenRule_createsImplicitRule() { mZenModeHelper.mConfig.automaticRules.clear(); @@ -6457,7 +6343,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void applyGlobalPolicyAsImplicitZenRule_updatesImplicitRule() { mZenModeHelper.mConfig.automaticRules.clear(); @@ -6489,7 +6374,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void applyGlobalPolicyAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() { mZenModeHelper.mConfig.automaticRules.clear(); String pkg = mContext.getPackageName(); @@ -6532,7 +6416,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void applyGlobalPolicyAsImplicitZenRule_ruleCustomizedButNotZenPolicy_updatesRule() { mZenModeHelper.mConfig.automaticRules.clear(); String pkg = mContext.getPackageName(); @@ -6572,7 +6455,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void applyGlobalPolicyAsImplicitZenRule_again_refreshesRuleName() { mZenModeHelper.mConfig.automaticRules.clear(); mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, @@ -6591,7 +6474,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void applyGlobalPolicyAsImplicitZenRule_again_doesNotChangeCustomizedRuleName() { mZenModeHelper.mConfig.automaticRules.clear(); mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, @@ -6617,19 +6500,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @DisableFlags(FLAG_MODES_API) - public void applyGlobalPolicyAsImplicitZenRule_flagOff_ignored() { - mZenModeHelper.mConfig.automaticRules.clear(); - - withoutWtfCrash( - () -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, - CUSTOM_PKG_NAME, CUSTOM_PKG_UID, new Policy(0, 0, 0))); - - assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty(); - } - - @Test - @EnableFlags(FLAG_MODES_API) public void getNotificationPolicyFromImplicitZenRule_returnsSetPolicy() { Policy writtenPolicy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED, @@ -6645,7 +6515,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) @DisableFlags(FLAG_MODES_UI) public void getNotificationPolicyFromImplicitZenRule_ruleWithoutPolicy_copiesGlobalPolicy() { // Implicit rule will get the global policy at the time of rule creation. @@ -6665,7 +6534,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void getNotificationPolicyFromImplicitZenRule_noImplicitRule_returnsGlobalPolicy() { Policy policy = new Policy(PRIORITY_CATEGORY_CALLS, PRIORITY_SENDERS_STARRED, 0); mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, policy, ORIGIN_APP, @@ -6680,7 +6548,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) @DisableFlags(FLAG_MODES_UI) public void setNotificationPolicy_updatesRulePolicies_ifRulePolicyIsDefaultOrGlobalPolicy() { ZenPolicy defaultZenPolicy = mZenModeHelper.getDefaultZenPolicy(); @@ -6726,7 +6593,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(Flags.FLAG_MODES_API) public void addRule_iconIdWithResourceNameTooLong_ignoresIcon() { int resourceId = 999; String veryLongResourceName = "com.android.server.notification:drawable/" @@ -6745,7 +6611,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setManualZenRuleDeviceEffects_noPreexistingMode() { ZenDeviceEffects effects = new ZenDeviceEffects.Builder() .setShouldDimWallpaper(true) @@ -6759,7 +6625,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setManualZenRuleDeviceEffects_preexistingMode() { mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, Uri.EMPTY, ORIGIN_USER_IN_SYSTEMUI, "create manual rule", "settings", SYSTEM_UID); @@ -6776,7 +6642,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void addAutomaticZenRule_startsDisabled_recordsDisabledOrigin() { AutomaticZenRule startsDisabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) .setOwner(new ComponentName(mPkg, "SomeProvider")) @@ -6792,7 +6658,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void updateAutomaticZenRule_disabling_recordsDisabledOrigin() { AutomaticZenRule startsEnabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) .setOwner(new ComponentName(mPkg, "SomeProvider")) @@ -6815,7 +6681,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void updateAutomaticZenRule_keepingDisabled_preservesPreviousDisabledOrigin() { AutomaticZenRule startsEnabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) .setOwner(new ComponentName(mPkg, "SomeProvider")) @@ -6845,7 +6711,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void updateAutomaticZenRule_enabling_clearsDisabledOrigin() { AutomaticZenRule startsEnabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) .setOwner(new ComponentName(mPkg, "SomeProvider")) @@ -6875,7 +6741,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setAutomaticZenRuleState_manualActivation_appliesOverride() { AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) .setPackage(mPkg) @@ -6893,7 +6759,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setAutomaticZenRuleState_manualActivationAndThenDeactivation_removesOverride() { AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) .setPackage(mPkg) @@ -6930,7 +6796,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setAutomaticZenRuleState_manualDeactivationAndThenReactivation_removesOverride() { AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) .setPackage(mPkg) @@ -6976,7 +6842,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setAutomaticZenRuleState_manualDeactivation_appliesOverride() { AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) .setPackage(mPkg) @@ -7002,7 +6868,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setAutomaticZenRuleState_ifManualActive_appCannotDeactivateBeforeActivating() { AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) .setPackage(mPkg) @@ -7039,7 +6905,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setAutomaticZenRuleState_ifManualInactive_appCannotReactivateBeforeDeactivating() { AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) .setPackage(mPkg) @@ -7085,7 +6951,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setAutomaticZenRuleState_withActivationOverride_userActionFromAppCanDeactivate() { AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) .setPackage(mPkg) @@ -7109,7 +6975,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setAutomaticZenRuleState_withDeactivationOverride_userActionFromAppCanActivate() { AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) .setPackage(mPkg) @@ -7140,7 +7006,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setAutomaticZenRuleState_manualActionFromApp_isNotOverride() { AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) .setPackage(mPkg) @@ -7165,7 +7031,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setAutomaticZenRuleState_implicitRuleManualActivation_doesNotUseOverride() { mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS, PERMISSION_GRANTED); // So that canManageAZR succeeds. @@ -7188,7 +7054,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setAutomaticZenRuleState_implicitRuleManualDeactivation_doesNotUseOverride() { mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS, PERMISSION_GRANTED); // So that canManageAZR succeeds. @@ -7214,24 +7080,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @DisableFlags({FLAG_MODES_API, FLAG_MODES_UI}) - public void testDefaultConfig_preModesApi_rulesAreBare() { - // Create a new user, which should get a copy of the default policy. - mZenModeHelper.onUserSwitched(101); - - ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get( - ZenModeConfig.EVENTS_OBSOLETE_RULE_ID); - - assertThat(eventsRule).isNotNull(); - assertThat(eventsRule.zenPolicy).isNull(); - assertThat(eventsRule.type).isEqualTo(TYPE_UNKNOWN); - assertThat(eventsRule.triggerDescription).isNull(); - } - - @Test - @EnableFlags(FLAG_MODES_API) @DisableFlags(FLAG_MODES_UI) - public void testDefaultConfig_modesApi_rulesHaveFullPolicy() { + public void testDefaultConfig_rulesHaveFullPolicy() { // Create a new user, which should get a copy of the default policy. mZenModeHelper.onUserSwitched(201); @@ -7245,7 +7095,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void testDefaultConfig_modesUi_rulesHaveFullPolicy() { // Create a new user, which should get a copy of the default policy. mZenModeHelper.onUserSwitched(301); @@ -7260,7 +7110,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setAutomaticZenRuleState_withManualActivation_activeOnReboot() throws Exception { AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) @@ -7298,7 +7148,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void setAutomaticZenRuleState_withManualDeactivation_clearedOnReboot() throws Exception { AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) @@ -7336,7 +7186,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_MODES_API) public void addAutomaticZenRule_withoutPolicy_getsItsOwnInstanceOfDefaultPolicy() { // Add a rule without policy -> uses default config AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) @@ -7352,7 +7201,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void readXml_withDisabledEventsRule_deletesIt() throws Exception { ZenRule rule = new ZenRule(); rule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID; @@ -7372,7 +7221,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void readXml_withEnabledEventsRule_keepsIt() throws Exception { ZenRule rule = new ZenRule(); rule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID; @@ -7392,7 +7241,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + @EnableFlags(FLAG_MODES_UI) public void updateHasPriorityChannels_keepsChannelSettings() { setupZenConfig(); @@ -7512,6 +7361,125 @@ public class ZenModeHelperTest extends UiServiceTestCase { "config: setAzrStateFromCps: cond/cond (ORIGIN_APP) from uid " + CUSTOM_PKG_UID); } + @Test + @EnableFlags({FLAG_MODES_UI, FLAG_MODES_CLEANUP_IMPLICIT}) + public void setAutomaticZenRuleState_updatesLastActivation() { + String ruleOne = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, + new AutomaticZenRule.Builder("rule", CONDITION_ID) + .setConfigurationActivity(new ComponentName(mPkg, "cls")) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .build(), + ORIGIN_APP, "reason", CUSTOM_PKG_UID); + String ruleTwo = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, + new AutomaticZenRule.Builder("unrelated", Uri.parse("other.condition")) + .setConfigurationActivity(new ComponentName(mPkg, "cls")) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .build(), + ORIGIN_APP, "reason", CUSTOM_PKG_UID); + + assertThat(getZenRule(ruleOne).lastActivation).isNull(); + assertThat(getZenRule(ruleTwo).lastActivation).isNull(); + + Instant firstActivation = Instant.ofEpochMilli(100); + mTestClock.setNow(firstActivation); + mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleOne, CONDITION_TRUE, + ORIGIN_APP, CUSTOM_PKG_UID); + + assertThat(getZenRule(ruleOne).lastActivation).isEqualTo(firstActivation); + assertThat(getZenRule(ruleTwo).lastActivation).isNull(); + + mTestClock.setNow(Instant.ofEpochMilli(300)); + mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleOne, CONDITION_FALSE, + ORIGIN_APP, CUSTOM_PKG_UID); + + assertThat(getZenRule(ruleOne).lastActivation).isEqualTo(firstActivation); + assertThat(getZenRule(ruleTwo).lastActivation).isNull(); + + Instant secondActivation = Instant.ofEpochMilli(500); + mTestClock.setNow(secondActivation); + mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleOne, CONDITION_TRUE, + ORIGIN_APP, CUSTOM_PKG_UID); + + assertThat(getZenRule(ruleOne).lastActivation).isEqualTo(secondActivation); + assertThat(getZenRule(ruleTwo).lastActivation).isNull(); + } + + @Test + @EnableFlags({FLAG_MODES_UI, FLAG_MODES_CLEANUP_IMPLICIT}) + public void setManualZenMode_updatesLastActivation() { + assertThat(mZenModeHelper.mConfig.manualRule.lastActivation).isNull(); + Instant instant = Instant.ofEpochMilli(100); + mTestClock.setNow(instant); + + mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_ALARMS, null, + ORIGIN_USER_IN_SYSTEMUI, "reason", "systemui", SYSTEM_UID); + + assertThat(mZenModeHelper.mConfig.manualRule.lastActivation).isEqualTo(instant); + } + + @Test + @EnableFlags({FLAG_MODES_UI, FLAG_MODES_CLEANUP_IMPLICIT}) + public void applyGlobalZenModeAsImplicitZenRule_updatesLastActivation() { + Instant instant = Instant.ofEpochMilli(100); + mTestClock.setNow(instant); + + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME, + CUSTOM_PKG_UID, ZEN_MODE_ALARMS); + + ZenRule implicitRule = getZenRule(implicitRuleId(CUSTOM_PKG_NAME)); + assertThat(implicitRule.lastActivation).isEqualTo(instant); + } + + @Test + @EnableFlags({FLAG_MODES_UI, FLAG_MODES_CLEANUP_IMPLICIT}) + public void setAutomaticZenRuleState_notChangingActiveState_doesNotUpdateLastActivation() { + String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, + new AutomaticZenRule.Builder("rule", CONDITION_ID) + .setConfigurationActivity(new ComponentName(mPkg, "cls")) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .build(), + ORIGIN_APP, "reason", CUSTOM_PKG_UID); + + assertThat(getZenRule(ruleId).lastActivation).isNull(); + + // Manual activation comes first + Instant firstActivation = Instant.ofEpochMilli(100); + mTestClock.setNow(firstActivation); + mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE, + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + + assertThat(getZenRule(ruleId).lastActivation).isEqualTo(firstActivation); + + // Now the app says the rule should be active (assume it's on a schedule, and the app + // doesn't listen to broadcasts so it doesn't know an override was present). This doesn't + // change the activation state. + mTestClock.setNow(Instant.ofEpochMilli(300)); + mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE, + ORIGIN_APP, CUSTOM_PKG_UID); + + assertThat(getZenRule(ruleId).lastActivation).isEqualTo(firstActivation); + } + + @Test + @EnableFlags({FLAG_MODES_UI, FLAG_MODES_CLEANUP_IMPLICIT}) + public void addOrUpdateRule_doesNotUpdateLastActivation() { + AutomaticZenRule azr = new AutomaticZenRule.Builder("rule", CONDITION_ID) + .setConfigurationActivity(new ComponentName(mPkg, "cls")) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .build(); + + String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, azr, + ORIGIN_APP, "reason", CUSTOM_PKG_UID); + + assertThat(getZenRule(ruleId).lastActivation).isNull(); + + mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, + new AutomaticZenRule.Builder(azr).setName("New name").build(), ORIGIN_APP, "reason", + CUSTOM_PKG_UID); + + assertThat(getZenRule(ruleId).lastActivation).isNull(); + } + private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode, @Nullable ZenPolicy zenPolicy) { ZenRule rule = new ZenRule(); @@ -7529,22 +7497,27 @@ public class ZenModeHelperTest extends UiServiceTestCase { } private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA = - Correspondence.transforming(zr -> { - Parcel p = Parcel.obtain(); - try { - zr.writeToParcel(p, 0); - p.setDataPosition(0); - ZenRule copy = new ZenRule(p); - copy.creationTime = 0; - copy.userModifiedFields = 0; - copy.zenPolicyUserModifiedFields = 0; - copy.zenDeviceEffectsUserModifiedFields = 0; - return copy; - } finally { - p.recycle(); - } - }, - "Ignoring timestamp and userModifiedFields"); + Correspondence.transforming( + ZenModeHelperTest::cloneWithoutMetadata, + ZenModeHelperTest::cloneWithoutMetadata, + "Ignoring timestamps and userModifiedFields"); + + private static ZenRule cloneWithoutMetadata(ZenRule rule) { + Parcel p = Parcel.obtain(); + try { + rule.writeToParcel(p, 0); + p.setDataPosition(0); + ZenRule copy = new ZenRule(p); + copy.creationTime = 0; + copy.userModifiedFields = 0; + copy.zenPolicyUserModifiedFields = 0; + copy.zenDeviceEffectsUserModifiedFields = 0; + copy.lastActivation = null; + return copy; + } finally { + p.recycle(); + } + } private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy, @Nullable Boolean conditionActive) { @@ -7574,7 +7547,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { return rule; } - // TODO: b/310620812 - Update setup methods to include allowChannels() when MODES_API is inlined private void setupZenConfig() { Policy customPolicy = new Policy(PRIORITY_CATEGORY_REMINDERS | PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_MESSAGES @@ -7583,7 +7555,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { PRIORITY_SENDERS_STARRED, PRIORITY_SENDERS_STARRED, SUPPRESSED_EFFECT_BADGE, - 0, + 0, // allows priority channels. CONVERSATION_SENDERS_IMPORTANT); mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, customPolicy, ORIGIN_UNKNOWN, 1); if (!Flags.modesUi()) { @@ -7599,8 +7571,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(STATE_ALLOW, dndProto.getCalls().getNumber()); assertEquals(PEOPLE_STARRED, dndProto.getAllowCallsFrom().getNumber()); assertEquals(STATE_ALLOW, dndProto.getMessages().getNumber()); + assertEquals(PEOPLE_STARRED, dndProto.getAllowMessagesFrom().getNumber()); assertEquals(STATE_ALLOW, dndProto.getEvents().getNumber()); assertEquals(STATE_ALLOW, dndProto.getRepeatCallers().getNumber()); + assertEquals(STATE_ALLOW, dndProto.getConversations().getNumber()); + assertEquals(CONV_IMPORTANT, dndProto.getAllowConversationsFrom().getNumber()); assertEquals(STATE_ALLOW, dndProto.getFullscreen().getNumber()); assertEquals(STATE_ALLOW, dndProto.getLights().getNumber()); assertEquals(STATE_ALLOW, dndProto.getPeek().getNumber()); @@ -7616,7 +7591,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { return; } - // When modes_api flag is on, the default zen config is the device defaults. + // The default zen config is the device defaults. assertThat(dndProto.getAlarms().getNumber()).isEqualTo(STATE_ALLOW); assertThat(dndProto.getMedia().getNumber()).isEqualTo(STATE_ALLOW); assertThat(dndProto.getSystem().getNumber()).isEqualTo(STATE_DISALLOW); @@ -7627,6 +7602,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(dndProto.getAllowMessagesFrom().getNumber()).isEqualTo(PEOPLE_STARRED); assertThat(dndProto.getEvents().getNumber()).isEqualTo(STATE_DISALLOW); assertThat(dndProto.getRepeatCallers().getNumber()).isEqualTo(STATE_ALLOW); + assertThat(dndProto.getConversations().getNumber()).isEqualTo(STATE_ALLOW); + assertThat(dndProto.getAllowConversationsFrom().getNumber()).isEqualTo(CONV_IMPORTANT); assertThat(dndProto.getFullscreen().getNumber()).isEqualTo(STATE_DISALLOW); assertThat(dndProto.getLights().getNumber()).isEqualTo(STATE_DISALLOW); assertThat(dndProto.getPeek().getNumber()).isEqualTo(STATE_DISALLOW); @@ -7634,6 +7611,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(dndProto.getBadge().getNumber()).isEqualTo(STATE_ALLOW); assertThat(dndProto.getAmbient().getNumber()).isEqualTo(STATE_DISALLOW); assertThat(dndProto.getNotificationList().getNumber()).isEqualTo(STATE_ALLOW); + assertThat(dndProto.getAllowChannels().getNumber()).isEqualTo( + DNDProtoEnums.CHANNEL_POLICY_PRIORITY); } private static String getZenLog() { @@ -7642,16 +7621,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { return zenLogWriter.toString(); } - private static void withoutWtfCrash(Runnable test) { - Log.TerribleFailureHandler oldHandler = Log.setWtfHandler((tag, what, system) -> { - }); - try { - test.run(); - } finally { - Log.setWtfHandler(oldHandler); - } - } - /** * Wrapper to use TypedXmlPullParser as XmlResourceParser for Resources.getXml() */ @@ -7954,6 +7923,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { return mNowMillis; } + private void setNow(Instant instant) { + mNowMillis = instant.toEpochMilli(); + } + private void setNowMillis(long millis) { mNowMillis = millis; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java index 6433b76defc3..8b9376454c0f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java @@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; -import android.app.Flags; import android.os.Parcel; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.ZenPolicy; @@ -34,7 +33,6 @@ import com.android.server.UiServiceTestCase; import com.google.protobuf.nano.InvalidProtocolBufferNanoException; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -50,11 +48,6 @@ public class ZenPolicyTest extends UiServiceTestCase { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); - @Before - public final void setUp() { - mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); - } - @Test public void testZenPolicyApplyAllowedToDisallowed() { ZenPolicy.Builder builder = new ZenPolicy.Builder(); @@ -207,8 +200,6 @@ public class ZenPolicyTest extends UiServiceTestCase { @Test public void testZenPolicyApplyChannels_applyUnset() { - mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); - ZenPolicy.Builder builder = new ZenPolicy.Builder(); ZenPolicy unset = builder.build(); @@ -223,8 +214,6 @@ public class ZenPolicyTest extends UiServiceTestCase { @Test public void testZenPolicyApplyChannels_applyStricter() { - mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); - ZenPolicy.Builder builder = new ZenPolicy.Builder(); builder.allowPriorityChannels(false); ZenPolicy none = builder.build(); @@ -239,8 +228,6 @@ public class ZenPolicyTest extends UiServiceTestCase { @Test public void testZenPolicyApplyChannels_applyLooser() { - mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); - ZenPolicy.Builder builder = new ZenPolicy.Builder(); builder.allowPriorityChannels(false); ZenPolicy none = builder.build(); @@ -255,8 +242,6 @@ public class ZenPolicyTest extends UiServiceTestCase { @Test public void testZenPolicyApplyChannels_applySet() { - mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); - ZenPolicy.Builder builder = new ZenPolicy.Builder(); ZenPolicy unset = builder.build(); @@ -270,8 +255,6 @@ public class ZenPolicyTest extends UiServiceTestCase { @Test public void testZenPolicyOverwrite_allUnsetPolicies() { - mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); - ZenPolicy.Builder builder = new ZenPolicy.Builder(); ZenPolicy unset = builder.build(); @@ -292,8 +275,6 @@ public class ZenPolicyTest extends UiServiceTestCase { @Test public void testZenPolicyOverwrite_someOverlappingFields_takeNewPolicy() { - mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); - ZenPolicy p1 = new ZenPolicy.Builder() .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS) .allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED) @@ -375,7 +356,6 @@ public class ZenPolicyTest extends UiServiceTestCase { @Test public void testEmptyZenPolicy_emptyChannels() { - mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); ZenPolicy.Builder builder = new ZenPolicy.Builder(); ZenPolicy policy = builder.build(); @@ -688,22 +668,7 @@ public class ZenPolicyTest extends UiServiceTestCase { } @Test - public void testAllowChannels_noFlag() { - mSetFlagsRule.disableFlags(Flags.FLAG_MODES_API); - - // allowChannels should be unset, not be modifiable, and not show up in any output - ZenPolicy.Builder builder = new ZenPolicy.Builder(); - builder.allowPriorityChannels(true); - ZenPolicy policy = builder.build(); - - assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET); - assertThat(policy.toString().contains("allowChannels")).isFalse(); - } - - @Test public void testAllowChannels() { - mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); - // allow priority channels ZenPolicy.Builder builder = new ZenPolicy.Builder(); builder.allowPriorityChannels(true); diff --git a/services/tests/wmtests/res/values/styles.xml b/services/tests/wmtests/res/values/styles.xml index 6857ff99e9b8..6ded2b557bcc 100644 --- a/services/tests/wmtests/res/values/styles.xml +++ b/services/tests/wmtests/res/values/styles.xml @@ -21,4 +21,14 @@ <item name="android:windowIsTranslucent">true</item> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> </style> + + <style name="ActivityWindowStyleTest"> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowIsFloating">true</item> + <item name="android:windowShowWallpaper">true</item> + <item name="android:windowNoDisplay">true</item> + <item name="android:windowDisablePreview">true</item> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> + <item name="android:windowSplashScreenBehavior">icon_preferred</item> + </style> </resources> diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java index 8e2cea7a8c78..6d8a48799112 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -244,9 +244,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L}, KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN, KeyEvent.KEYCODE_L, META_ON}, - {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N}, - KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES, KeyEvent.KEYCODE_N, - META_ON | CTRL_ON}, {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN}, KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE, diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java index 32a3b7f2c9cc..22c86eb3a9b8 100644 --- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java @@ -272,6 +272,19 @@ public class PhoneWindowManagerTests { } @Test + public void powerPress_withoutDreamManagerInternal_doesNotCrash() { + when(mDisplayPolicy.isAwake()).thenReturn(true); + mDreamManagerInternal = null; + initPhoneWindowManager(); + + // Power button pressed. + int eventTime = 0; + mPhoneWindowManager.powerPress(eventTime, 1, 0); + + // verify no crash + } + + @Test public void powerPress_hubOrDreamOrSleep_hubAvailableLocks() { when(mDisplayPolicy.isAwake()).thenReturn(true); mContext.getTestablePermissions().setPermission(android.Manifest.permission.DEVICE_POWER, @@ -352,5 +365,10 @@ public class PhoneWindowManagerTests { WindowWakeUpPolicy getWindowWakeUpPolicy() { return mock(WindowWakeUpPolicy.class); } + + @Override + DreamManagerInternal getDreamManagerInternal() { + return mDreamManagerInternal; + } } } 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 9e7575f1c644..f5bda9f2b9e3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -104,7 +104,6 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { .setTask(mTrampolineActivity.getTask()) .setComponent(createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, "TopActivity")) .build(); - mTopActivity.mDisplayContent.mOpeningApps.add(mTopActivity); mTransition = new Transition(TRANSIT_OPEN, 0 /* flags */, mTopActivity.mTransitionController, createTestBLASTSyncEngine()); mTransition.mParticipants.add(mTopActivity); @@ -485,7 +484,6 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { @Test public void testActivityDrawnWithoutTransition() { - mTopActivity.mDisplayContent.mOpeningApps.remove(mTopActivity); mTransition.mParticipants.remove(mTopActivity); onIntentStarted(mTopActivity.intent); notifyAndVerifyActivityLaunched(mTopActivity); 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 bb296148dad1..d53ba1d24d8c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -992,6 +992,27 @@ public class ActivityRecordTests extends WindowTestsBase { assertEquals(persistentSavedState, activity.getPersistentSavedState()); } + @Test + public void testReadWindowStyle() { + final ActivityRecord activity = new ActivityBuilder(mAtm).setActivityTheme( + com.android.frameworks.wmtests.R.style.ActivityWindowStyleTest).build(); + assertTrue(activity.isNoDisplay()); + assertTrue("Fill parent because showWallpaper", activity.mStyleFillsParent); + + final ActivityRecord.WindowStyle style = mAtm.getWindowStyle( + activity.packageName, activity.info.theme, activity.mUserId); + assertNotNull(style); + assertTrue(style.isTranslucent()); + assertTrue(style.isFloating()); + assertTrue(style.showWallpaper()); + assertTrue(style.noDisplay()); + assertTrue(style.disablePreview()); + assertTrue(style.optOutEdgeToEdge()); + assertEquals(1 /* icon_preferred */, style.mSplashScreenBehavior); + assertEquals(style.noDisplay(), mAtm.mInternal.isNoDisplay(activity.packageName, + activity.info.theme, activity.mUserId)); + } + /** * Verify that activity finish request is not performed if activity is finishing or is in * incorrect state. @@ -3326,16 +3347,13 @@ public class ActivityRecordTests extends WindowTestsBase { makeWindowVisibleAndDrawn(app); // Put the activity in close transition. - mDisplayContent.mOpeningApps.clear(); - mDisplayContent.mClosingApps.add(app.mActivityRecord); - mDisplayContent.prepareAppTransition(TRANSIT_CLOSE); + requestTransition(app.mActivityRecord, WindowManager.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(); app.mActivityRecord.prepareSurfaces(); - assertTrue(mDisplayContent.mClosingApps.contains(app.mActivityRecord)); assertFalse(app.mActivityRecord.isVisibleRequested()); assertTrue(app.mActivityRecord.isVisible()); assertTrue(app.mActivityRecord.isSurfaceShowing()); @@ -3353,11 +3371,6 @@ public class ActivityRecordTests extends WindowTestsBase { makeWindowVisibleAndDrawn(app); app.mActivityRecord.prepareSurfaces(); - // 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); 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 3c74ad06a21f..a9be47d71213 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java @@ -509,6 +509,32 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue(); } + @Test + public void testOpaque_nonLeafTaskFragmentWithDirectActivity_opaque() { + final ActivityRecord directChildActivity = new ActivityBuilder(mAtm).setCreateTask(true) + .build(); + directChildActivity.setOccludesParent(true); + final Task nonLeafTask = directChildActivity.getTask(); + final TaskFragment directChildFragment = new TaskFragment(mAtm, new Binder(), + true /* createdByOrganizer */, false /* isEmbedded */); + nonLeafTask.addChild(directChildFragment, 0); + + assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(nonLeafTask)).isTrue(); + } + + @Test + public void testOpaque_nonLeafTaskFragmentWithDirectActivity_transparent() { + final ActivityRecord directChildActivity = new ActivityBuilder(mAtm).setCreateTask(true) + .build(); + directChildActivity.setOccludesParent(false); + final Task nonLeafTask = directChildActivity.getTask(); + final TaskFragment directChildFragment = new TaskFragment(mAtm, new Binder(), + true /* createdByOrganizer */, false /* isEmbedded */); + nonLeafTask.addChild(directChildFragment, 0); + + assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(nonLeafTask)).isFalse(); + } + @NonNull private TaskFragment createChildTaskFragment(@NonNull Task parent, @WindowConfiguration.WindowingMode int windowingMode, diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java index 018ea58e7120..d016e735f0c7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java @@ -151,6 +151,10 @@ class AppCompatActivityRobot { doReturn(taskBounds).when(mTaskStack.top()).getBounds(); } + void configureTaskAppBounds(@NonNull Rect appBounds) { + mTaskStack.top().getWindowConfiguration().setAppBounds(appBounds); + } + void configureTopActivityBounds(@NonNull Rect activityBounds) { doReturn(activityBounds).when(mActivityStack.top()).getBounds(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java index 0c310324ce67..2603d787ae37 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java @@ -16,7 +16,9 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -27,6 +29,7 @@ import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; import android.graphics.Rect; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.InsetsSource; import android.view.InsetsState; @@ -40,6 +43,7 @@ import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.window.flags.Flags; import org.junit.Test; import org.junit.runner.RunWith; @@ -225,6 +229,53 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase { }); } + @Test + @EnableFlags(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS) + public void testGetRoundedCornersRadius_letterboxBoundsMatchHeightInFreeform_notRounded() { + runTestScenario((robot) -> { + robot.conf().setLetterboxActivityCornersRadius(15); + robot.configureWindowState(); + robot.activity().createActivityWithComponent(); + robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false); + robot.activity().setTopActivityVisible(/* isVisible */ true); + robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true); + robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true); + robot.resources().configureGetDimensionPixelSize(R.dimen.taskbar_frame_height, 20); + + robot.activity().setTaskWindowingMode(WINDOWING_MODE_FREEFORM); + final Rect appBounds = new Rect(0, 0, 100, 500); + robot.configureWindowStateFrame(appBounds); + robot.activity().configureTaskAppBounds(appBounds); + + robot.startLetterbox(); + + robot.checkWindowStateRoundedCornersRadius(/* expected */ 0); + }); + } + + @Test + @EnableFlags(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS) + public void testGetRoundedCornersRadius_letterboxBoundsNotMatchHeightInFreeform_rounded() { + runTestScenario((robot) -> { + robot.conf().setLetterboxActivityCornersRadius(15); + robot.configureWindowState(); + robot.activity().createActivityWithComponent(); + robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false); + robot.activity().setTopActivityVisible(/* isVisible */ true); + robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true); + robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true); + robot.resources().configureGetDimensionPixelSize(R.dimen.taskbar_frame_height, 20); + + robot.activity().setTaskWindowingMode(WINDOWING_MODE_FREEFORM); + robot.configureWindowStateFrame(new Rect(0, 0, 500, 200)); + robot.activity().configureTaskAppBounds(new Rect(0, 0, 500, 500)); + + robot.startLetterbox(); + + robot.checkWindowStateRoundedCornersRadius(/* expected */ 15); + }); + } + /** * Runs a test scenario providing a Robot. */ @@ -265,6 +316,10 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase { spyOn(getTransparentPolicy()); } + void startLetterbox() { + getAppCompatLetterboxPolicy().start(mWindowState); + } + void configureWindowStateWithTaskBar(boolean hasInsetsRoundedCorners) { configureWindowState(/* withTaskBar */ true, hasInsetsRoundedCorners); } @@ -273,6 +328,10 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase { configureWindowState(/* withTaskBar */ false, /* hasInsetsRoundedCorners */ false); } + void configureWindowStateFrame(@NonNull Rect frame) { + doReturn(frame).when(mWindowState).getFrame(); + } + void configureInsetsRoundedCorners(@NonNull RoundedCorners roundedCorners) { mInsetsState.setRoundedCorners(roundedCorners); } @@ -290,6 +349,7 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase { } mWindowState.mInvGlobalScale = 1f; final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(); + attrs.type = TYPE_BASE_APPLICATION; doReturn(mInsetsState).when(mWindowState).getInsetsState(); doReturn(attrs).when(mWindowState).getAttrs(); doReturn(true).when(mWindowState).isDrawn(); diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index fbb123e25b29..e08197155f03 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -294,6 +294,14 @@ public class BackNavigationControllerTests extends WindowTestsBase { backNavigationInfo = startBackNavigation(); assertThat(typeToString(backNavigationInfo.getType())) .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY)); + + // reset drawing status, test previous activity has no process. + backNavigationInfo.onBackNavigationFinished(false); + mBackNavigationController.clearBackAnimations(true); + doReturn(false).when(testCase.recordBack).hasProcess(); + backNavigationInfo = startBackNavigation(); + assertThat(typeToString(backNavigationInfo.getType())) + .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java index eaffc481098e..1e91bedb5c18 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java @@ -16,8 +16,6 @@ package com.android.server.wm; -import static android.provider.Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; @@ -30,7 +28,6 @@ import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; -import android.provider.Settings; import android.window.DesktopModeFlags; import androidx.test.filters.SmallTest; @@ -74,14 +71,12 @@ public class DesktopModeHelperTest { doReturn(mContext.getContentResolver()).when(mMockContext).getContentResolver(); resetDesktopModeFlagsCache(); resetEnforceDeviceRestriction(); - resetFlagOverride(); } @After public void tearDown() throws Exception { resetDesktopModeFlagsCache(); resetEnforceDeviceRestriction(); - resetFlagOverride(); } @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, @@ -167,7 +162,8 @@ public class DesktopModeHelperTest { @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION) @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) @Test - public void canEnterDesktopMode_DWFlagEnabled_configDevOptionOn_flagOverrideOn_returnsTrue() { + public void canEnterDesktopMode_DWFlagEnabled_configDevOptionOn_flagOverrideOn_returnsTrue() + throws Exception { doReturn(true).when(mMockResources).getBoolean( eq(R.bool.config_isDesktopModeDevOptionSupported) ); @@ -177,22 +173,41 @@ public class DesktopModeHelperTest { } @Test - public void isDeviceEligibleForDesktopMode_configDEModeOn_returnsTrue() { + public void isDeviceEligibleForDesktopMode_configDEModeOnAndIntDispHostsDesktop_returnsTrue() { + doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported)); + doReturn(true).when(mMockResources) + .getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops)); + + assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue(); + } + + @Test + public void isDeviceEligibleForDesktopMode_configDEModeOffAndIntDispHostsDesktop_returnsFalse() { + doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported)); + doReturn(false).when(mMockResources) + .getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops)); + + assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse(); + } + + @Test + public void isDeviceEligibleForDesktopMode_configDEModeOnAndIntDispHostsDesktopOff_returnsFalse() { + doReturn(false).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported)); doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops)); - assertThat(DesktopModeHelper.isInternalDisplayEligibleToHostDesktops(mMockContext)).isTrue(); + assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse(); } @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION) @Test public void isDeviceEligibleForDesktopMode_supportFlagOff_returnsFalse() { - assertThat(DesktopModeHelper.isInternalDisplayEligibleToHostDesktops(mMockContext)).isFalse(); + assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse(); } @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION) @Test public void isDeviceEligibleForDesktopMode_supportFlagOn_returnsFalse() { - assertThat(DesktopModeHelper.isInternalDisplayEligibleToHostDesktops(mMockContext)).isFalse(); + assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse(); } @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION) @@ -202,7 +217,7 @@ public class DesktopModeHelperTest { eq(R.bool.config_isDesktopModeDevOptionSupported) ); - assertThat(DesktopModeHelper.isInternalDisplayEligibleToHostDesktops(mMockContext)).isTrue(); + assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue(); } private void resetEnforceDeviceRestriction() throws Exception { @@ -227,13 +242,10 @@ public class DesktopModeHelperTest { cachedToggleOverride.set(/* obj= */ null, /* value= */ null); } - private void resetFlagOverride() { - Settings.Global.putString(mContext.getContentResolver(), - DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, null); - } - - private void setFlagOverride(DesktopModeFlags.ToggleOverride override) { - Settings.Global.putInt(mContext.getContentResolver(), - DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, override.getSetting()); + private void setFlagOverride(DesktopModeFlags.ToggleOverride override) throws Exception { + Field cachedToggleOverride = DesktopModeFlags.class.getDeclaredField( + "sCachedToggleOverride"); + cachedToggleOverride.setAccessible(true); + cachedToggleOverride.set(/* obj= */ null, /* value= */ override); } }
\ No newline at end of file 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 82435b24dad6..d5b9751b0f51 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -164,6 +164,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.BooleanSupplier; /** * Tests for the {@link DisplayContent} class. @@ -1799,8 +1800,7 @@ public class DisplayContentTests extends WindowTestsBase { final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build(); app.setVisible(false); app.setState(ActivityRecord.State.RESUMED, "test"); - mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN); - mDisplayContent.mOpeningApps.add(app); + requestTransition(app, WindowManager.TRANSIT_OPEN); final int newOrientation = getRotatedOrientation(mDisplayContent); app.setRequestedOrientation(newOrientation); @@ -2674,16 +2674,67 @@ public class DisplayContentTests extends WindowTestsBase { public void testKeyguardGoingAwayWhileAodShown() { mDisplayContent.getDisplayPolicy().setAwake(true); - final WindowState appWin = newWindowBuilder("appWin", TYPE_APPLICATION).setDisplay( - mDisplayContent).build(); - final ActivityRecord activity = appWin.mActivityRecord; + final KeyguardController keyguard = mAtm.mKeyguardController; + final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final int displayId = mDisplayContent.getDisplayId(); + + final BooleanSupplier keyguardShowing = () -> keyguard.isKeyguardShowing(displayId); + final BooleanSupplier keyguardGoingAway = () -> keyguard.isKeyguardGoingAway(displayId); + final BooleanSupplier appVisible = activity::isVisibleRequested; + + // Begin locked and in AOD + keyguard.setKeyguardShown(displayId, true /* keyguard */, true /* aod */); + assertFalse(keyguardGoingAway.getAsBoolean()); + assertFalse(appVisible.getAsBoolean()); + + // Start unlocking from AOD. + keyguard.keyguardGoingAway(displayId, 0x0 /* flags */); + assertTrue(keyguardGoingAway.getAsBoolean()); + assertTrue(appVisible.getAsBoolean()); - mAtm.mKeyguardController.setKeyguardShown(appWin.getDisplayId(), true /* keyguardShowing */, - true /* aodShowing */); - assertFalse(activity.isVisibleRequested()); + // Clear AOD. This does *not* clear the going-away status. + keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */); + assertTrue(keyguardGoingAway.getAsBoolean()); + assertTrue(appVisible.getAsBoolean()); + + // Finish unlock + keyguard.setKeyguardShown(displayId, false /* keyguard */, false /* aod */); + assertFalse(keyguardGoingAway.getAsBoolean()); + assertTrue(appVisible.getAsBoolean()); + } + + @Test + public void testKeyguardGoingAwayCanceledWhileAodShown() { + mDisplayContent.getDisplayPolicy().setAwake(true); - mAtm.mKeyguardController.keyguardGoingAway(appWin.getDisplayId(), 0 /* flags */); - assertTrue(activity.isVisibleRequested()); + final KeyguardController keyguard = mAtm.mKeyguardController; + final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final int displayId = mDisplayContent.getDisplayId(); + + final BooleanSupplier keyguardShowing = () -> keyguard.isKeyguardShowing(displayId); + final BooleanSupplier keyguardGoingAway = () -> keyguard.isKeyguardGoingAway(displayId); + final BooleanSupplier appVisible = activity::isVisibleRequested; + + // Begin locked and in AOD + keyguard.setKeyguardShown(displayId, true /* keyguard */, true /* aod */); + assertFalse(keyguardGoingAway.getAsBoolean()); + assertFalse(appVisible.getAsBoolean()); + + // Start unlocking from AOD. + keyguard.keyguardGoingAway(displayId, 0x0 /* flags */); + assertTrue(keyguardGoingAway.getAsBoolean()); + assertTrue(appVisible.getAsBoolean()); + + // Clear AOD. This does *not* clear the going-away status. + keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */); + assertTrue(keyguardGoingAway.getAsBoolean()); + assertTrue(appVisible.getAsBoolean()); + + // Same API call a second time cancels the unlock, because AOD isn't changing. + keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */); + assertTrue(keyguardShowing.getAsBoolean()); + assertFalse(keyguardGoingAway.getAsBoolean()); + assertFalse(appVisible.getAsBoolean()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java index db90c28ec7df..2d4101e40615 100644 --- a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java @@ -17,17 +17,21 @@ package com.android.server.wm; import static android.view.Display.FLAG_PRESENTATION; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OPEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; +import android.annotation.NonNull; import android.graphics.Rect; import android.os.UserHandle; -import android.os.UserManager; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; @@ -41,6 +45,7 @@ import android.view.WindowManagerGlobal; import androidx.test.filters.SmallTest; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -53,35 +58,100 @@ import org.junit.runner.RunWith; @RunWith(WindowTestRunner.class) public class PresentationControllerTests extends WindowTestsBase { + TestTransitionPlayer mPlayer; + + @Before + public void setUp() { + mPlayer = registerTestTransitionPlayer(); + } + @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS) @Test - public void testPresentationHidesActivitiesBehind() { - final DisplayInfo displayInfo = new DisplayInfo(); - displayInfo.copyFrom(mDisplayInfo); - displayInfo.flags = FLAG_PRESENTATION; - final DisplayContent dc = createNewDisplay(displayInfo); - final int displayId = dc.getDisplayId(); - doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId); + public void testPresentationShowAndHide() { + final DisplayContent dc = createPresentationDisplay(); final ActivityRecord activity = createActivityRecord(createTask(dc)); assertTrue(activity.isVisible()); - doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled()); - final int uid = 100000; // uid for non-system user + // Add a presentation window, which requests the activity to stop. + final WindowState window = addPresentationWindow(100000, dc.mDisplayId); + assertFalse(activity.isVisibleRequested()); + assertTrue(activity.isVisible()); + final Transition addTransition = window.mTransitionController.getCollectingTransition(); + assertEquals(TRANSIT_OPEN, addTransition.mType); + assertTrue(addTransition.isInTransition(window)); + assertTrue(addTransition.isInTransition(activity)); + + // Completing the transition makes the activity invisible. + completeTransition(addTransition, /*abortSync=*/ true); + assertFalse(activity.isVisible()); + + // Remove a Presentation window, which requests the activity to be resumed back. + window.removeIfPossible(); + final Transition removeTransition = window.mTransitionController.getCollectingTransition(); + assertEquals(TRANSIT_CLOSE, removeTransition.mType); + assertTrue(removeTransition.isInTransition(window)); + assertTrue(removeTransition.isInTransition(activity)); + assertTrue(activity.isVisibleRequested()); + assertFalse(activity.isVisible()); + + // Completing the transition makes the activity visible. + completeTransition(removeTransition, /*abortSync=*/ false); + assertTrue(activity.isVisible()); + } + + @DisableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS) + @Test + public void testPresentationShowAndHide_flagDisabled() { + final DisplayContent dc = createPresentationDisplay(); + final ActivityRecord activity = createActivityRecord(createTask(dc)); + assertTrue(activity.isVisible()); + + final WindowState window = addPresentationWindow(100000, dc.mDisplayId); + assertFalse(window.mTransitionController.isCollecting()); + assertTrue(activity.isVisibleRequested()); + assertTrue(activity.isVisible()); + + window.removeIfPossible(); + assertFalse(window.mTransitionController.isCollecting()); + assertTrue(activity.isVisibleRequested()); + assertTrue(activity.isVisible()); + assertFalse(window.isAttached()); + } + + private WindowState addPresentationWindow(int uid, int displayId) { final Session session = createTestSession(mAtm, 1234 /* pid */, uid); final int userId = UserHandle.getUserId(uid); - doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId)); + doReturn(true).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId)); final WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.TYPE_PRESENTATION); - final IWindow clientWindow = new TestIWindow(); - final int result = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId, + final int res = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId, userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(), new InsetsSourceControl.Array(), new Rect(), new float[1]); - assertTrue(result >= WindowManagerGlobal.ADD_OKAY); - assertFalse(activity.isVisible()); - + assertTrue(res >= WindowManagerGlobal.ADD_OKAY); final WindowState window = mWm.windowForClientLocked(session, clientWindow, false); - window.removeImmediately(); - assertTrue(activity.isVisible()); + window.mHasSurface = true; + return window; + } + + private DisplayContent createPresentationDisplay() { + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.copyFrom(mDisplayInfo); + displayInfo.flags = FLAG_PRESENTATION; + final DisplayContent dc = createNewDisplay(displayInfo); + final int displayId = dc.getDisplayId(); + doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId); + return dc; + } + + private void completeTransition(@NonNull Transition transition, boolean abortSync) { + final ActionChain chain = ActionChain.testFinish(transition); + if (abortSync) { + // Forcefully finishing the active sync for testing purpose. + mWm.mSyncEngine.abort(transition.getSyncId()); + } else { + transition.onTransactionReady(transition.getSyncId(), mTransaction); + } + transition.finishTransition(chain); } } 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 95bca2b17efb..1dc32b00acba 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -4486,6 +4486,49 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + @EnableFlags(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS) + @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) + public void testInFreeform_boundsSandboxedToAppBounds() { + allowDesktopMode(); + final int dw = 2800; + final int dh = 1400; + final int notchHeight = 100; + final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh) + .setNotch(notchHeight) + .build(); + setUpApp(display); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + + mTask.mDisplayContent.getDefaultTaskDisplayArea() + .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); + mTask.setWindowingMode(WINDOWING_MODE_FREEFORM); + Rect appBounds = new Rect(0, 0, 1000, 500); + Rect bounds = new Rect(0, 0, 1000, 600); + mTask.getWindowConfiguration().setAppBounds(appBounds); + mTask.getWindowConfiguration().setBounds(bounds); + mActivity.onConfigurationChanged(mTask.getConfiguration()); + + // Bounds are sandboxed to appBounds in freeform. + assertDownScaled(); + assertEquals(mActivity.getWindowConfiguration().getAppBounds(), + mActivity.getWindowConfiguration().getBounds()); + + // Exit freeform. + mTask.mDisplayContent.getDefaultTaskDisplayArea() + .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); + mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mTask.getWindowConfiguration().setBounds(new Rect(0, 0, dw, dh)); + mActivity.onConfigurationChanged(mTask.getConfiguration()); + assertFitted(); + appBounds = mActivity.getWindowConfiguration().getAppBounds(); + bounds = mActivity.getWindowConfiguration().getBounds(); + // Bounds are not sandboxed to appBounds. + assertNotEquals(appBounds, bounds); + assertEquals(notchHeight, appBounds.top - bounds.top); + } + + + @Test @EnableFlags(Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES) public void testUserAspectRatioOverridesNotAppliedToResizeableFreeformActivity() { final TaskBuilder taskBuilder = 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 59335d3bb135..9406779c929d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -98,6 +98,7 @@ import com.android.server.policy.PermissionPolicyInternal; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.testutils.StubTransaction; import com.android.server.uri.UriGrantsManagerInternal; +import com.android.window.flags.Flags; import org.junit.rules.TestRule; import org.junit.runner.Description; @@ -657,6 +658,13 @@ public class SystemServicesTestRule implements TestRule { AppWarnings appWarnings = getAppWarningsLocked(); spyOn(appWarnings); doNothing().when(appWarnings).onStartActivity(any()); + + if (Flags.trackSystemUiContextBeforeWms()) { + final Context uiContext = getUiContext(); + spyOn(uiContext); + doNothing().when(uiContext).registerComponentCallbacks(any()); + doNothing().when(uiContext).unregisterComponentCallbacks(any()); + } } @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 7ab55bf7e874..cc2a76dcc9f2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -189,11 +189,12 @@ public class TaskFragmentTest extends WindowTestsBase { doReturn(true).when(mTaskFragment).isVisible(); doReturn(true).when(mTaskFragment).isVisibleRequested(); + spyOn(mTaskFragment.mTransitionController); clearInvocations(mTransaction); mTaskFragment.setBounds(endBounds); // No change transition, but update the organized surface position. - verify(mTaskFragment, never()).initializeChangeTransition(any(), any()); + verify(mTaskFragment.mTransitionController, never()).collectVisibleChange(any()); verify(mTransaction).setPosition(mLeash, endBounds.left, endBounds.top); } 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 ccce57a81e41..b1525cf00dc6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -30,6 +30,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.Rect; @@ -39,6 +40,7 @@ import android.view.DisplayCutout; import android.view.DisplayInfo; import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry; +import com.android.window.flags.Flags; class TestDisplayContent extends DisplayContent { @@ -79,6 +81,13 @@ class TestDisplayContent extends DisplayContent { WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget()); WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getTransientControlTarget()); + if (Flags.trackSystemUiContextBeforeWms()) { + final Context uiContext = getDisplayUiContext(); + spyOn(uiContext); + doNothing().when(uiContext).registerComponentCallbacks(any()); + doNothing().when(uiContext).unregisterComponentCallbacks(any()); + } + // For devices that set the sysprop ro.bootanim.set_orientation_<display_id> // See DisplayRotation#readDefaultDisplayRotation for context. // Without that, meaning of height and width in context of the tests can be swapped if 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 2ee34d3a4b36..77ad7f7bac7f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -683,7 +683,7 @@ public class TransitionTests extends WindowTestsBase { app.onStartingWindowDrawn(); // The task appeared event should be deferred until transition ready. assertFalse(task.taskAppearedReady()); - testPlayer.onTransactionReady(app.getSyncTransaction()); + testPlayer.onTransactionReady(); assertTrue(task.taskAppearedReady()); assertTrue(playerProc.isRunningRemoteTransition()); assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread())); @@ -1162,7 +1162,8 @@ public class TransitionTests extends WindowTestsBase { screenDecor.updateSurfacePosition(mMockT); assertEquals(prevPos, screenDecor.mLastSurfacePosition); - final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction startTransaction = mTransaction; + clearInvocations(startTransaction); final SurfaceControl.TransactionCommittedListener transactionCommittedListener = onRotationTransactionReady(player, startTransaction); @@ -1213,7 +1214,8 @@ public class TransitionTests extends WindowTestsBase { assertFalse(statusBar.mToken.inTransition()); assertTrue(app.getTask().inTransition()); - final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction startTransaction = mTransaction; + clearInvocations(startTransaction); final SurfaceControl leash = statusBar.mToken.getAnimationLeash(); doReturn(true).when(leash).isValid(); final SurfaceControl.TransactionCommittedListener transactionCommittedListener = @@ -1287,7 +1289,8 @@ public class TransitionTests extends WindowTestsBase { // Avoid DeviceStateController disturbing the test by triggering another rotation change. doReturn(false).when(mDisplayContent).updateRotationUnchecked(); - onRotationTransactionReady(player, mWm.mTransactionFactory.get()).onTransactionCommitted(); + clearInvocations(mTransaction); + onRotationTransactionReady(player, mTransaction).onTransactionCommitted(); assertEquals(ROTATION_ANIMATION_SEAMLESS, player.mLastReady.getChange( mDisplayContent.mRemoteToken.toWindowContainerToken()).getRotationAnimation()); spyOn(navBarInsetsProvider); @@ -1350,7 +1353,7 @@ public class TransitionTests extends WindowTestsBase { mDisplayContent.setFixedRotationLaunchingAppUnchecked(home); doReturn(true).when(home).hasFixedRotationTransform(any()); player.startTransition(); - player.onTransactionReady(mDisplayContent.getSyncTransaction()); + player.onTransactionReady(); final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation(); final RemoteDisplayChangeController displayChangeController = mDisplayContent @@ -3071,8 +3074,11 @@ public class TransitionTests extends WindowTestsBase { TestTransitionPlayer player, SurfaceControl.Transaction startTransaction) { final ArgumentCaptor<SurfaceControl.TransactionCommittedListener> listenerCaptor = ArgumentCaptor.forClass(SurfaceControl.TransactionCommittedListener.class); - player.onTransactionReady(startTransaction); - verify(startTransaction).addTransactionCommittedListener(any(), listenerCaptor.capture()); + player.onTransactionReady(); + // The startTransaction is from mWm.mTransactionFactory.get() in SyncGroup#finishNow. + // 2 times are from SyncGroup#finishNow and AsyncRotationController#setupStartTransaction. + verify(startTransaction, times(2)).addTransactionCommittedListener( + any(), listenerCaptor.capture()); return listenerCaptor.getValue(); } } 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 edffab801499..cee98fb1b34c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -409,17 +409,6 @@ public class WindowContainerTests extends WindowTestsBase { } @Test - public void testIsAnimating_TransitionFlag() { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm); - final TestWindowContainer root = builder.setLayer(0).build(); - final TestWindowContainer child1 = root.addChildWindow( - builder.setWaitForTransitionStart(true)); - - assertFalse(root.isAnimating(TRANSITION)); - assertTrue(child1.isAnimating(TRANSITION)); - } - - @Test public void testIsAnimating_ParentsFlag() { final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm); final TestWindowContainer root = builder.setLayer(0).build(); @@ -1655,7 +1644,7 @@ public class WindowContainerTests extends WindowTestsBase { }; TestWindowContainer(WindowManagerService wm, int layer, boolean isAnimating, - boolean isVisible, boolean waitTransitStart, Integer orientation, WindowState ws) { + boolean isVisible, Integer orientation, WindowState ws) { super(wm); mLayer = layer; @@ -1663,7 +1652,6 @@ public class WindowContainerTests extends WindowTestsBase { mIsVisible = isVisible; mFillsParent = true; mOrientation = orientation; - mWaitForTransitStart = waitTransitStart; mWindowState = ws; spyOn(mSurfaceAnimator); doReturn(mIsAnimating).when(mSurfaceAnimator).isAnimating(); @@ -1729,11 +1717,6 @@ public class WindowContainerTests extends WindowTestsBase { } @Override - boolean isWaitingForTransitionStart() { - return mWaitForTransitStart; - } - - @Override WindowState asWindowState() { return mWindowState; } @@ -1744,7 +1727,6 @@ public class WindowContainerTests extends WindowTestsBase { private int mLayer; private boolean mIsAnimating; private boolean mIsVisible; - private boolean mIsWaitTransitStart; private Integer mOrientation; private WindowState mWindowState; @@ -1782,14 +1764,9 @@ public class WindowContainerTests extends WindowTestsBase { return this; } - TestWindowContainerBuilder setWaitForTransitionStart(boolean waitTransitStart) { - mIsWaitTransitStart = waitTransitStart; - return this; - } - TestWindowContainer build() { return new TestWindowContainer(mWm, mLayer, mIsAnimating, mIsVisible, - mIsWaitTransitStart, mOrientation, mWindowState); + mOrientation, mWindowState); } } 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 1dfb20a41816..d228970e0371 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION; import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -33,9 +34,11 @@ 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.ConfigurationContainer.applySizeOverrideIfNeeded; 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.any; @@ -58,6 +61,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.LocaleList; import android.os.RemoteException; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import org.junit.Before; @@ -453,6 +457,56 @@ public class WindowProcessControllerTests extends WindowTestsBase { assertEquals(topDisplayArea, mWpc.getTopActivityDisplayArea()); } + @Test + @EnableFlags(com.android.window.flags.Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION) + public void testOverrideConfigurationApplied() { + final DisplayContent displayContent = new TestDisplayContent.Builder(mAtm, 1000, 1500) + .setSystemDecorations(true).setDensityDpi(160).build(); + final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); + // Setup the decor insets info. + final DisplayPolicy.DecorInsets.Info decorInsetsInfo = new DisplayPolicy.DecorInsets.Info(); + final Rect emptyRect = new Rect(); + decorInsetsInfo.mNonDecorInsets.set(emptyRect); + decorInsetsInfo.mConfigInsets.set(emptyRect); + decorInsetsInfo.mOverrideConfigInsets.set(new Rect(0, 100, 0, 200)); + decorInsetsInfo.mOverrideNonDecorInsets.set(new Rect(0, 0, 0, 200)); + decorInsetsInfo.mNonDecorFrame.set(new Rect(0, 0, 1000, 1500)); + decorInsetsInfo.mConfigFrame.set(new Rect(0, 0, 1000, 1500)); + decorInsetsInfo.mOverrideConfigFrame.set(new Rect(0, 100, 1000, 1300)); + decorInsetsInfo.mOverrideNonDecorFrame.set(new Rect(0, 0, 1000, 1300)); + doReturn(decorInsetsInfo).when(displayPolicy) + .getDecorInsetsInfo(anyInt(), anyInt(), anyInt()); + + final Configuration newParentConfig = displayContent.getConfiguration(); + final Configuration resolvedConfig = new Configuration(); + + // Mock the app info to not enforce the decoupled configuration to apply the override. + final ApplicationInfo appInfo = mock(ApplicationInfo.class); + doReturn(false).when(appInfo) + .isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED); + doReturn(false).when(appInfo) + .isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION); + + // No value should be set before override. + assertNull(resolvedConfig.windowConfiguration.getAppBounds()); + applySizeOverrideIfNeeded( + displayContent, + appInfo, + newParentConfig, + resolvedConfig, + false /* optsOutEdgeToEdge */, + false /* hasFixedRotationTransform */, + false /* hasCompatDisplayInsets */, + null /* task */); + + // Assert the override config insets are applied. + // Status bars, and all non-decor insets should be deducted for the config screen size. + assertEquals(1200, resolvedConfig.screenHeightDp); + // Only the non-decor insets should be deducted for the app bounds. + assertNotNull(resolvedConfig.windowConfiguration.getAppBounds()); + assertEquals(1300, resolvedConfig.windowConfiguration.getAppBounds().height()); + } + private TestDisplayContent createTestDisplayContentInContainer() { return new TestDisplayContent.Builder(mAtm, 1000, 1500).build(); } 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 7f9e591ca5e3..c6416850c464 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -861,11 +861,9 @@ public class WindowTestsBase extends SystemServiceTestsBase { /** Creates a {@link DisplayContent} and adds it to the system. */ private DisplayContent createNewDisplay(DisplayInfo info, @DisplayImePolicy int imePolicy, @Nullable SettingsEntry overrideSettings) { - final DisplayContent display = - new TestDisplayContent.Builder(mAtm, info) - .setOverrideSettings(overrideSettings) - .build(); - final DisplayContent dc = display.mDisplayContent; + final DisplayContent dc = new TestDisplayContent.Builder(mAtm, info) + .setOverrideSettings(overrideSettings) + .build(); // this display can show IME. dc.mWmService.mDisplayWindowSettings.setDisplayImePolicy(dc, imePolicy); return dc; @@ -2161,13 +2159,14 @@ public class WindowTestsBase extends SystemServiceTestsBase { mOrganizer.startTransition(mLastTransit.getToken(), null); } - void onTransactionReady(SurfaceControl.Transaction t) { - mLastTransit.onTransactionReady(mLastTransit.getSyncId(), t); + void onTransactionReady() { + // SyncGroup#finishNow -> Transition#onTransactionReady. + mController.mSyncEngine.abort(mLastTransit.getSyncId()); } void start() { startTransition(); - onTransactionReady(mock(SurfaceControl.Transaction.class)); + onTransactionReady(); } public void finish() { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java index 12b744546f5e..9367941e32a3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java @@ -18,12 +18,16 @@ package com.android.server.wm; import static android.tools.traces.Utils.busyWaitForDataSourceRegistration; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + 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.verifyZeroInteractions; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -34,11 +38,15 @@ import android.platform.test.annotations.Presubmit; import android.tools.ScenarioBuilder; import android.tools.traces.io.ResultWriter; import android.tools.traces.monitors.PerfettoTraceMonitor; +import android.util.Log; import android.view.Choreographer; +import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; +import androidx.test.uiautomator.UiDevice; import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -51,14 +59,15 @@ import java.io.IOException; /** * Test class for {@link WindowTracingPerfetto}. */ +@FlakyTest(bugId = 372558379) @SmallTest @Presubmit public class WindowTracingPerfettoTest { private static final String TEST_DATA_SOURCE_NAME = "android.windowmanager.test"; private static WindowManagerService sWmMock; - private static Choreographer sChoreographer; private static WindowTracing sWindowTracing; + private static Boolean sIsDataSourceRegisteredSuccessfully; private PerfettoTraceMonitor mTraceMonitor; @@ -66,19 +75,39 @@ public class WindowTracingPerfettoTest { public static void setUpOnce() throws Exception { sWmMock = Mockito.mock(WindowManagerService.class); Mockito.doNothing().when(sWmMock).dumpDebugLocked(Mockito.any(), Mockito.anyInt()); - sChoreographer = Mockito.mock(Choreographer.class); - sWindowTracing = new WindowTracingPerfetto(sWmMock, sChoreographer, + sWindowTracing = new WindowTracingPerfetto(sWmMock, Mockito.mock(Choreographer.class), new WindowManagerGlobalLock(), TEST_DATA_SOURCE_NAME); - busyWaitForDataSourceRegistration(TEST_DATA_SOURCE_NAME); + } + + @AfterClass + public static void tearDownOnce() { + sWmMock = null; + sWindowTracing = null; } @Before public void setUp() throws IOException { - Mockito.clearInvocations(sWmMock); + if (sIsDataSourceRegisteredSuccessfully != null) { + assumeTrue("Failed to register data source", sIsDataSourceRegisteredSuccessfully); + return; + } + try { + busyWaitForDataSourceRegistration(TEST_DATA_SOURCE_NAME); + sIsDataSourceRegisteredSuccessfully = true; + } catch (Exception e) { + sIsDataSourceRegisteredSuccessfully = false; + final String perfettoStatus = UiDevice.getInstance(getInstrumentation()) + .executeShellCommand("perfetto --query"); + Log.e(WindowTracingPerfettoTest.class.getSimpleName(), + "Failed to register data source: " + perfettoStatus); + // Only fail once. The rest tests will be skipped by assumeTrue. + fail("Failed to register data source"); + } } @After public void tearDown() throws IOException { + Mockito.clearInvocations(sWmMock); stopTracing(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/WindowStyleCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/WindowStyleCacheTest.java new file mode 100644 index 000000000000..57a340129456 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/WindowStyleCacheTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2025 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.utils; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; + +import android.content.Context; +import android.content.res.TypedArray; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.internal.policy.AttributeCache; + +import org.junit.Test; + +/** + * Build/Install/Run: + * atest WmTests:WindowStyleCacheTest + */ +@SmallTest +@Presubmit +public class WindowStyleCacheTest { + + @Test + public void testCache() { + final Context context = getInstrumentation().getContext(); + AttributeCache.init(context); + final WindowStyleCache<TestStyle> cache = new WindowStyleCache<>(TestStyle::new); + final String packageName = context.getPackageName(); + final int theme = com.android.frameworks.wmtests.R.style.ActivityWindowStyleTest; + final int userId = context.getUserId(); + final TestStyle style = cache.get(packageName, theme, userId); + assertNotNull(style); + assertSame(style, cache.get(packageName, theme, userId)); + + cache.invalidatePackage(packageName); + assertNotSame(style, cache.get(packageName, theme, userId)); + } + + private static class TestStyle { + TestStyle(TypedArray array) { + } + } +} diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index ec4f7e1ea4ba..4395b76d91cc 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -52,6 +52,7 @@ import android.os.Bundle; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.service.usb.UsbServiceDumpProto; @@ -694,6 +695,11 @@ public class UsbService extends IUsbManager.Stub { return (getCurrentFunctions() & UsbManager.usbFunctionsFromString(function)) != 0; } + @Override + public boolean isUvcGadgetSupportEnabled() { + return SystemProperties.getBoolean("ro.usb.uvc.enabled", false); + } + @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB) @Override public long getCurrentFunctions() { |