diff options
Diffstat (limited to 'services')
108 files changed, 2966 insertions, 1468 deletions
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index b52b3dabd47d..35db3c6f0a6d 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -260,6 +260,16 @@ flag { } flag { + name: "pointer_up_motion_event_in_touch_exploration" + namespace: "accessibility" + description: "Allows POINTER_UP motionEvents to trigger during touch exploration." + bug: "374930391" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "proxy_use_apps_on_virtual_device_listener" namespace: "accessibility" description: "Fixes race condition described in b/286587811" @@ -336,3 +346,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "hearing_input_change_when_comm_device" + namespace: "accessibility" + description: "Listen to the CommunicationDeviceChanged to show hearing device input notification." + bug: "394070235" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index da36c6d8187d..6c26c1f74002 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -521,15 +521,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Nullable IBinder focusedToken) { return AccessibilityManagerService.this.handleKeyGestureEvent(event); } - - @Override - public boolean isKeyGestureSupported(int gestureType) { - return switch (gestureType) { - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION, - KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK -> true; - default -> false; - }; - } }; @VisibleForTesting diff --git a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java index 10dffb59317e..805d7f820c8d 100644 --- a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java @@ -65,9 +65,9 @@ public class HearingDevicePhoneCallNotificationController { private final Executor mCallbackExecutor; public HearingDevicePhoneCallNotificationController(@NonNull Context context) { - mTelephonyListener = new CallStateListener(context); mTelephonyManager = context.getSystemService(TelephonyManager.class); mCallbackExecutor = Executors.newSingleThreadExecutor(); + mTelephonyListener = new CallStateListener(context, mCallbackExecutor); } @VisibleForTesting @@ -109,14 +109,29 @@ public class HearingDevicePhoneCallNotificationController { AudioDeviceAttributes.ROLE_INPUT, AudioDeviceInfo.TYPE_BUILTIN_MIC, ""); private final Context mContext; + private final Executor mCommDeviceChangedExecutor; + private final AudioManager.OnCommunicationDeviceChangedListener mCommDeviceChangedListener; private NotificationManager mNotificationManager; private AudioManager mAudioManager; private BroadcastReceiver mHearingDeviceActionReceiver; private BluetoothDevice mHearingDevice; + private boolean mIsCommDeviceChangedRegistered = false; private boolean mIsNotificationShown = false; - CallStateListener(@NonNull Context context) { + CallStateListener(@NonNull Context context, @NonNull Executor executor) { mContext = context; + mCommDeviceChangedExecutor = executor; + mCommDeviceChangedListener = device -> { + if (device == null) { + return; + } + mHearingDevice = getSupportedInputHearingDeviceInfo(List.of(device)); + if (mHearingDevice != null) { + showNotificationIfNeeded(); + } else { + dismissNotificationIfNeeded(); + } + }; } @Override @@ -134,6 +149,11 @@ public class HearingDevicePhoneCallNotificationController { } if (state == TelephonyManager.CALL_STATE_IDLE) { + if (mIsCommDeviceChangedRegistered) { + mIsCommDeviceChangedRegistered = false; + mAudioManager.removeOnCommunicationDeviceChangedListener( + mCommDeviceChangedListener); + } dismissNotificationIfNeeded(); if (mHearingDevice != null) { @@ -143,10 +163,23 @@ public class HearingDevicePhoneCallNotificationController { mHearingDevice = null; } if (state == TelephonyManager.CALL_STATE_OFFHOOK) { - mHearingDevice = getSupportedInputHearingDeviceInfo( - mAudioManager.getAvailableCommunicationDevices()); - if (mHearingDevice != null) { - showNotificationIfNeeded(); + if (com.android.server.accessibility.Flags.hearingInputChangeWhenCommDevice()) { + AudioDeviceInfo commDevice = mAudioManager.getCommunicationDevice(); + mHearingDevice = getSupportedInputHearingDeviceInfo(List.of(commDevice)); + if (mHearingDevice != null) { + showNotificationIfNeeded(); + } else { + mAudioManager.addOnCommunicationDeviceChangedListener( + mCommDeviceChangedExecutor, + mCommDeviceChangedListener); + mIsCommDeviceChangedRegistered = true; + } + } else { + mHearingDevice = getSupportedInputHearingDeviceInfo( + mAudioManager.getAvailableCommunicationDevices()); + if (mHearingDevice != null) { + showNotificationIfNeeded(); + } } } } @@ -264,6 +297,10 @@ public class HearingDevicePhoneCallNotificationController { PendingIntent.FLAG_IMMUTABLE); } case ACTION_BLUETOOTH_DEVICE_DETAILS -> { + if (mHearingDevice == null) { + return null; + } + Bundle bundle = new Bundle(); bundle.putString(KEY_BLUETOOTH_ADDRESS, mHearingDevice.getAddress()); intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle); 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 739ea0df87ab..a71224a68125 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java @@ -124,6 +124,22 @@ public class AutoclickController extends BaseEventStreamTransformation { } }; + @VisibleForTesting + final AutoclickScrollPanel.ScrollPanelControllerInterface mScrollPanelController = + new AutoclickScrollPanel.ScrollPanelControllerInterface() { + @Override + public void handleScroll(@AutoclickScrollPanel.ScrollDirection int direction) { + // TODO(b/388845721): Perform actual scroll. + } + + @Override + public void exitScrollMode() { + if (mAutoclickScrollPanel != null) { + mAutoclickScrollPanel.hide(); + } + } + }; + public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) { mTrace = trace; mContext = context; @@ -168,7 +184,8 @@ public class AutoclickController extends BaseEventStreamTransformation { mWindowManager = mContext.getSystemService(WindowManager.class); mAutoclickTypePanel = new AutoclickTypePanel(mContext, mWindowManager, mUserId, clickPanelController); - mAutoclickScrollPanel = new AutoclickScrollPanel(mContext, mWindowManager); + mAutoclickScrollPanel = new AutoclickScrollPanel(mContext, mWindowManager, + mScrollPanelController); mAutoclickTypePanel.show(); mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams()); @@ -248,7 +265,11 @@ public class AutoclickController extends BaseEventStreamTransformation { private boolean isPaused() { return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused() - && !mAutoclickTypePanel.isHovered(); + && !isHovered(); + } + + private boolean isHovered() { + return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isHovered(); } private void cancelPendingClick() { @@ -495,6 +516,8 @@ public class AutoclickController extends BaseEventStreamTransformation { private int mEventPolicyFlags; /** Current meta state. This value will be used as meta state for click event sequence. */ private int mMetaState; + /** Last observed panel hovered state when click was scheduled. */ + private boolean mHoveredState; /** * The current anchor's coordinates. Should be ignored if #mLastMotionEvent is null. @@ -648,6 +671,7 @@ public class AutoclickController extends BaseEventStreamTransformation { } mLastMotionEvent = MotionEvent.obtain(event); mEventPolicyFlags = policyFlags; + mHoveredState = isHovered(); if (useAsAnchor) { final int pointerIndex = mLastMotionEvent.getActionIndex(); @@ -729,14 +753,18 @@ public class AutoclickController extends BaseEventStreamTransformation { final long now = SystemClock.uptimeMillis(); - // TODO(b/395094903): always triggers left-click when the cursor hovers over the - // autoclick type panel, to always allow users to change a different click type. - // Otherwise, if one chooses the right-click, this user won't be able to rely on - // autoclick to select other click types. - final int actionButton = - mActiveClickType == AUTOCLICK_TYPE_RIGHT_CLICK - ? BUTTON_SECONDARY - : BUTTON_PRIMARY; + int actionButton; + if (mHoveredState) { + // Always triggers left-click when the cursor hovers over the autoclick type + // panel, to always allow users to change a different click type. Otherwise, if + // one chooses the right-click, this user won't be able to rely on autoclick to + // select other click types. + actionButton = BUTTON_PRIMARY; + } else { + actionButton = mActiveClickType == AUTOCLICK_TYPE_RIGHT_CLICK + ? BUTTON_SECONDARY + : BUTTON_PRIMARY; + } MotionEvent downEvent = MotionEvent.obtain( diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java index 86f79a83ea28..e79be502a6fc 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java @@ -18,6 +18,7 @@ package com.android.server.accessibility.autoclick; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import android.annotation.IntDef; import android.content.Context; import android.graphics.PixelFormat; import android.view.Gravity; @@ -25,23 +26,97 @@ import android.view.LayoutInflater; import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; +import android.widget.ImageButton; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.internal.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + public class AutoclickScrollPanel { + public static final int DIRECTION_UP = 0; + public static final int DIRECTION_DOWN = 1; + public static final int DIRECTION_LEFT = 2; + public static final int DIRECTION_RIGHT = 3; + + @IntDef({ + DIRECTION_UP, + DIRECTION_DOWN, + DIRECTION_LEFT, + DIRECTION_RIGHT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ScrollDirection {} + private final Context mContext; private final View mContentView; private final WindowManager mWindowManager; + private ScrollPanelControllerInterface mScrollPanelController; + + // Scroll panel buttons. + private final ImageButton mUpButton; + private final ImageButton mDownButton; + private final ImageButton mLeftButton; + private final ImageButton mRightButton; + private final ImageButton mExitButton; + private boolean mInScrollMode = false; - public AutoclickScrollPanel(Context context, WindowManager windowManager) { + /** + * Interface for handling scroll operations. + */ + public interface ScrollPanelControllerInterface { + /** + * Called when a scroll direction is hovered. + * + * @param direction The direction to scroll: one of {@link ScrollDirection} values. + */ + void handleScroll(@ScrollDirection int direction); + + /** + * Called when the exit button is hovered. + */ + void exitScrollMode(); + } + + public AutoclickScrollPanel(Context context, WindowManager windowManager, + ScrollPanelControllerInterface controller) { mContext = context; mWindowManager = windowManager; + mScrollPanelController = controller; mContentView = LayoutInflater.from(context).inflate( R.layout.accessibility_autoclick_scroll_panel, null); + + // Initialize buttons. + mUpButton = mContentView.findViewById(R.id.scroll_up); + mLeftButton = mContentView.findViewById(R.id.scroll_left); + mRightButton = mContentView.findViewById(R.id.scroll_right); + mDownButton = mContentView.findViewById(R.id.scroll_down); + mExitButton = mContentView.findViewById(R.id.scroll_exit); + + initializeButtonState(); + } + + /** + * Sets up hover listeners for scroll panel buttons. + */ + private void initializeButtonState() { + // Set up hover listeners for direction buttons. + setupHoverListenerForDirectionButton(mUpButton, DIRECTION_UP); + setupHoverListenerForDirectionButton(mLeftButton, DIRECTION_LEFT); + setupHoverListenerForDirectionButton(mRightButton, DIRECTION_RIGHT); + setupHoverListenerForDirectionButton(mDownButton, DIRECTION_DOWN); + + // Set up hover listener for exit button. + mExitButton.setOnHoverListener((v, event) -> { + if (mScrollPanelController != null) { + mScrollPanelController.exitScrollMode(); + } + return true; + }); } /** @@ -67,6 +142,19 @@ public class AutoclickScrollPanel { } /** + * Sets up a hover listener for a direction button. + */ + private void setupHoverListenerForDirectionButton(ImageButton button, + @ScrollDirection int direction) { + button.setOnHoverListener((v, event) -> { + if (mScrollPanelController != null) { + mScrollPanelController.handleScroll(direction); + } + return true; + }); + } + + /** * Retrieves the layout params for AutoclickScrollPanel, used when it's added to the Window * Manager. */ diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index fb329430acb2..b02fe2752a62 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -653,6 +653,14 @@ public class TouchExplorer extends BaseEventStreamTransformation case ACTION_UP: handleActionUp(event, rawEvent, policyFlags); break; + case ACTION_POINTER_UP: + if (com.android.server.accessibility.Flags + .pointerUpMotionEventInTouchExploration()) { + if (mState.isServiceDetectingGestures()) { + mAms.sendMotionEventToListeningServices(rawEvent); + } + } + break; default: break; } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java index cd46b38272c2..568abd196735 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java @@ -26,6 +26,8 @@ import android.view.Display; import android.view.MotionEvent; import android.view.accessibility.AccessibilityEvent; +import androidx.annotation.VisibleForTesting; + import com.android.server.accessibility.AccessibilityManagerService; /** @@ -73,7 +75,8 @@ public class TouchState { private int mState = STATE_CLEAR; // Helper class to track received pointers. // Todo: collapse or hide this class so multiple classes don't modify it. - private final ReceivedPointerTracker mReceivedPointerTracker; + @VisibleForTesting + public final ReceivedPointerTracker mReceivedPointerTracker; // The most recently received motion event. private MotionEvent mLastReceivedEvent; // The accompanying raw event without any transformations. @@ -219,8 +222,19 @@ public class TouchState { startTouchInteracting(); break; case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END: - setState(STATE_CLEAR); - // We will clear when we actually handle the next ACTION_DOWN. + // When interaction ends, check if there are still down pointers. + // If there are any down pointers, go directly to TouchExploring instead. + if (com.android.server.accessibility.Flags + .pointerUpMotionEventInTouchExploration()) { + if (mReceivedPointerTracker.mReceivedPointersDown > 0) { + startTouchExploring(); + } else { + setState(STATE_CLEAR); + // We will clear when we actually handle the next ACTION_DOWN. + } + } else { + setState(STATE_CLEAR); + } break; case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: startTouchExploring(); @@ -419,7 +433,8 @@ public class TouchState { private final PointerDownInfo[] mReceivedPointers = new PointerDownInfo[MAX_POINTER_COUNT]; // Which pointers are down. - private int mReceivedPointersDown; + @VisibleForTesting + public int mReceivedPointersDown; // The edge flags of the last received down event. private int mLastReceivedDownEdgeFlags; diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index 6ccf5e47ca6c..59566677b1fc 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -39,6 +39,14 @@ import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_ import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__MENU; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_DELAY_AFTER_ANIMATION_END; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_FILL_DIALOG_DISABLED; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_LAST_TRIGGERED_ID_CHANGED; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_SCREEN_HAS_CREDMAN_FIELD; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_TIMEOUT_AFTER_DELAY; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_TIMEOUT_SINCE_IME_ANIMATED; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_UNKNOWN; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_WAIT_FOR_IME_ANIMATION; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_ACTIVITY_FINISHED; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_FILL_REQUEST_FAILED; @@ -157,8 +165,24 @@ public final class PresentationStatsEventLogger { DETECTION_PREFER_PCC }) @Retention(RetentionPolicy.SOURCE) - public @interface DetectionPreference { - } + public @interface DetectionPreference {} + + /** + * The fill dialog not shown reason. These are wrappers around + * {@link com.android.os.AtomsProto.AutofillPresentationEventReported.FillDialogNotShownReason}. + */ + @IntDef(prefix = {"FILL_DIALOG_NOT_SHOWN_REASON"}, value = { + FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN, + FILL_DIALOG_NOT_SHOWN_REASON_FILL_DIALOG_DISABLED, + FILL_DIALOG_NOT_SHOWN_REASON_SCREEN_HAS_CREDMAN_FIELD, + FILL_DIALOG_NOT_SHOWN_REASON_LAST_TRIGGERED_ID_CHANGED, + FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION, + FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED, + FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END, + FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_AFTER_DELAY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FillDialogNotShownReason {} public static final int NOT_SHOWN_REASON_ANY_SHOWN = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN; @@ -219,6 +243,25 @@ public final class PresentationStatsEventLogger { public static final int DETECTION_PREFER_PCC = AUTOFILL_FILL_RESPONSE_REPORTED__DETECTION_PREFERENCE__DETECTION_PREFER_PCC; + // Values for AutofillFillResponseReported.fill_dialog_not_shown_reason + public static final int FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_UNKNOWN; + public static final int FILL_DIALOG_NOT_SHOWN_REASON_FILL_DIALOG_DISABLED = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_FILL_DIALOG_DISABLED; + public static final int FILL_DIALOG_NOT_SHOWN_REASON_SCREEN_HAS_CREDMAN_FIELD = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_SCREEN_HAS_CREDMAN_FIELD; + public static final int FILL_DIALOG_NOT_SHOWN_REASON_LAST_TRIGGERED_ID_CHANGED = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_LAST_TRIGGERED_ID_CHANGED; + public static final int FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_WAIT_FOR_IME_ANIMATION; + public static final int FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_TIMEOUT_SINCE_IME_ANIMATED; + public static final int FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_DELAY_AFTER_ANIMATION_END; + public static final int FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_AFTER_DELAY = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_TIMEOUT_AFTER_DELAY; + + private static final int DEFAULT_VALUE_INT = -1; private final int mSessionId; @@ -871,6 +914,43 @@ public final class PresentationStatsEventLogger { } /** + * Set fill_dialog_not_shown_reason + * @param reason + */ + public void maybeSetFillDialogNotShownReason(@FillDialogNotShownReason int reason) { + mEventInternal.ifPresent(event -> { + if ((event.mFillDialogNotShownReason + == FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END + || event.mFillDialogNotShownReason + == FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION) && reason + == FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED) { + event.mFillDialogNotShownReason = FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_AFTER_DELAY; + } else { + event.mFillDialogNotShownReason = reason; + } + }); + } + + /** + * Set fill_dialog_ready_to_show_ms + * @param val + */ + public void maybeSetFillDialogReadyToShowMs(long val) { + mEventInternal.ifPresent(event -> { + event.mFillDialogReadyToShowMs = (int) (val - mSessionStartTimestamp); + }); + } + + /** + * Set ime_animation_finish_ms + * @param val + */ + public void maybeSetImeAnimationFinishMs(long val) { + mEventInternal.ifPresent(event -> { + event.mImeAnimationFinishMs = (int) (val - mSessionStartTimestamp); + }); + } + /** * Set the log contains relayout metrics. * This is being added as a temporary measure to add logging. * In future, when we map Session's old view states to the new autofill id's as part of fixing @@ -959,7 +1039,13 @@ public final class PresentationStatsEventLogger { + " event.notExpiringResponseDuringAuthCount=" + event.mFixExpireResponseDuringAuthCount + " event.notifyViewEnteredIgnoredDuringAuthCount=" - + event.mNotifyViewEnteredIgnoredDuringAuthCount); + + event.mNotifyViewEnteredIgnoredDuringAuthCount + + " event.fillDialogNotShownReason=" + + event.mFillDialogNotShownReason + + " event.fillDialogReadyToShowMs=" + + event.mFillDialogReadyToShowMs + + " event.imeAnimationFinishMs=" + + event.mImeAnimationFinishMs); } // TODO(b/234185326): Distinguish empty responses from other no presentation reasons. @@ -1020,7 +1106,10 @@ public final class PresentationStatsEventLogger { event.mViewFilledSuccessfullyOnRefillCount, event.mViewFailedOnRefillCount, event.mFixExpireResponseDuringAuthCount, - event.mNotifyViewEnteredIgnoredDuringAuthCount); + event.mNotifyViewEnteredIgnoredDuringAuthCount, + event.mFillDialogNotShownReason, + event.mFillDialogReadyToShowMs, + event.mImeAnimationFinishMs); mEventInternal = Optional.empty(); } @@ -1087,6 +1176,9 @@ public final class PresentationStatsEventLogger { // Following are not logged and used only for internal logic boolean shouldResetShownCount = false; boolean mHasRelayoutLog = false; + @FillDialogNotShownReason int mFillDialogNotShownReason = FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN; + int mFillDialogReadyToShowMs = DEFAULT_VALUE_INT; + int mImeAnimationFinishMs = DEFAULT_VALUE_INT; PresentationStatsEventInternal() {} } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 6515b237519a..ff3bf2acb080 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -81,6 +81,13 @@ import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTIC import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_RESULT_SUCCESS; import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_DATASET_AUTHENTICATION; import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_FULL_AUTHENTICATION; +import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END; +import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_FILL_DIALOG_DISABLED; +import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_LAST_TRIGGERED_ID_CHANGED; +import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_SCREEN_HAS_CREDMAN_FIELD; +import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED; +import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN; +import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_ANY_SHOWN; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_NO_FOCUS; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_FAILED; @@ -5622,6 +5629,10 @@ final class Session synchronized (mLock) { final ViewState currentView = mViewStates.get(mCurrentViewId); currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN); + // Set fill_dialog_not_shown_reason to unknown (a.k.a shown). It is needed due + // to possible SHOW_FILL_DIALOG_WAIT. + mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason( + FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN); } // Just show fill dialog once per fill request, so disabled after shown. // Note: Cannot disable before requestShowFillDialog() because the method @@ -5725,6 +5736,15 @@ final class Session private boolean isFillDialogUiEnabled() { synchronized (mLock) { + if (mSessionFlags.mFillDialogDisabled) { + mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason( + FILL_DIALOG_NOT_SHOWN_REASON_FILL_DIALOG_DISABLED); + } + if (mSessionFlags.mScreenHasCredmanField) { + // Prefer to log "HAS_CREDMAN_FIELD" over "FILL_DIALOG_DISABLED". + mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason( + FILL_DIALOG_NOT_SHOWN_REASON_SCREEN_HAS_CREDMAN_FIELD); + } return !mSessionFlags.mFillDialogDisabled && !mSessionFlags.mScreenHasCredmanField; } } @@ -5789,6 +5809,8 @@ final class Session || !ArrayUtils.contains(mLastFillDialogTriggerIds, filledId)) { // Last fill dialog triggered ids are changed. if (sDebug) Log.w(TAG, "Last fill dialog triggered ids are changed."); + mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason( + FILL_DIALOG_NOT_SHOWN_REASON_LAST_TRIGGERED_ID_CHANGED); return SHOW_FILL_DIALOG_NO; } @@ -5815,6 +5837,8 @@ final class Session // we need to wait for animation to happen. We can't return from here yet. // This is the situation #2 described above. Log.d(TAG, "Waiting for ime animation to complete before showing fill dialog"); + mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason( + FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION); mFillDialogRunnable = createFillDialogEvalRunnable( response, filledId, filterText, flags); return SHOW_FILL_DIALOG_WAIT; @@ -5824,9 +5848,15 @@ final class Session // max of start input time or the ime finish time long effectiveDuration = currentTimestampMs - Math.max(mLastInputStartTime, mImeAnimationFinishTimeMs); + mPresentationStatsEventLogger.maybeSetFillDialogReadyToShowMs( + currentTimestampMs); + mPresentationStatsEventLogger.maybeSetImeAnimationFinishMs( + Math.max(mLastInputStartTime, mImeAnimationFinishTimeMs)); if (effectiveDuration >= mFillDialogTimeoutMs) { Log.d(TAG, "Fill dialog not shown since IME has been up for more time than " + mFillDialogTimeoutMs + "ms"); + mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason( + FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED); return SHOW_FILL_DIALOG_NO; } else if (effectiveDuration < mFillDialogMinWaitAfterImeAnimationMs) { // we need to wait for some time after animation ends @@ -5834,6 +5864,8 @@ final class Session response, filledId, filterText, flags); mHandler.postDelayed(runnable, mFillDialogMinWaitAfterImeAnimationMs - effectiveDuration); + mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason( + FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END); return SHOW_FILL_DIALOG_WAIT; } } diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java index ebb1194c7c4a..b173f76e5f6f 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java +++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java @@ -25,6 +25,7 @@ import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_A import android.annotation.UserIdInt; import android.app.ApplicationThreadConstants; import android.app.IBackupAgent; +import android.app.backup.BackupManagerMonitor; import android.app.backup.BackupTransport; import android.app.backup.FullBackupDataOutput; import android.content.pm.ApplicationInfo; @@ -268,6 +269,12 @@ public class FullBackupEngine { mBackupManagerMonitorEventSender.monitorAgentLoggingResults(mPkg, mAgent); } catch (IOException e) { Slog.e(TAG, "Error backing up " + mPkg.packageName + ": " + e.getMessage()); + // This is likely due to the app process dying. + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN, + mPkg, + BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, + /* extras= */ null); result = BackupTransport.AGENT_ERROR; } finally { try { diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java index f677c9dbf4d0..48d21c3a222f 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java +++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java @@ -294,6 +294,14 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba // SinglePackageBackupPreflight. if (cancellationReason == CancellationReason.TIMEOUT) { Slog.wtf(TAG, "This task cannot time out"); + return; + } + + // We don't cancel the entire operation if a single agent is disconnected unexpectedly. + // SinglePackageBackupRunner and SinglePackageBackupPreflight will receive the same + // callback and fail gracefully. The operation should then continue to the next package. + if (cancellationReason == CancellationReason.AGENT_DISCONNECTED) { + return; } if (mCancelled) { diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index 1263146fe405..20f103cdfab4 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -589,6 +589,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { monitoringExtras); Slog.e(TAG, "Failure getting next package name"); EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE); + mStatus = BackupTransport.TRANSPORT_ERROR; nextState = UnifiedRestoreState.FINAL; return; } else if (mRestoreDescription == RestoreDescription.NO_MORE_PACKAGES) { diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java index fad59d23a6dc..855c72acd7ca 100644 --- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java +++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java @@ -389,6 +389,8 @@ public class BackupManagerMonitorDumpsysUtils { "Agent failure during restore"; case BackupManagerMonitor.LOG_EVENT_ID_FAILED_TO_READ_DATA_FROM_TRANSPORT -> "Failed to read data from Transport"; + case BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN -> + "LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN"; default -> "Unknown log event ID: " + code; }; return id; diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java index cd9285cdfe91..cbee8391458d 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java @@ -58,13 +58,16 @@ import android.os.Handler; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; +import android.util.ArraySet; import android.util.Slog; import com.android.internal.R; import com.android.server.companion.CompanionDeviceManagerService; import com.android.server.companion.utils.PackageUtils; +import java.util.Arrays; import java.util.List; +import java.util.Set; /** * Class responsible for handling incoming {@link AssociationRequest}s. @@ -130,6 +133,12 @@ public class AssociationRequestsProcessor { private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5; private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min; + // Set of profiles for which the association dialog cannot be skipped. + private static final Set<String> DEVICE_PROFILES_WITH_REQUIRED_CONFIRMATION = new ArraySet<>( + Arrays.asList( + AssociationRequest.DEVICE_PROFILE_APP_STREAMING, + AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING)); + private final @NonNull Context mContext; private final @NonNull PackageManagerInternal mPackageManagerInternal; private final @NonNull AssociationStore mAssociationStore; @@ -174,6 +183,7 @@ public class AssociationRequestsProcessor { // 2a. Check if association can be created without launching UI (i.e. CDM needs NEITHER // to perform discovery NOR to collect user consent). if (request.isSelfManaged() && !request.isForceConfirmation() + && !DEVICE_PROFILES_WITH_REQUIRED_CONFIRMATION.contains(request.getDeviceProfile()) && !willAddRoleHolder(request, packageName, userId)) { // 2a.1. Create association right away. createAssociationAndNotifyApplication(request, packageName, userId, diff --git a/services/core/Android.bp b/services/core/Android.bp index 14d9d3f0c0a1..decac40d20f8 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -122,10 +122,10 @@ genrule { } genrule { - name: "statslog-mediarouter-java-gen", - tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --java $(out) --module mediarouter --javaPackage com.android.server.media --javaClass MediaRouterStatsLog", - out: ["com/android/server/media/MediaRouterStatsLog.java"], + name: "statslog-mediarouter-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module mediarouter --javaPackage com.android.server.media --javaClass MediaRouterStatsLog", + out: ["com/android/server/media/MediaRouterStatsLog.java"], } java_library_static { @@ -138,6 +138,7 @@ java_library_static { "ondeviceintelligence_conditionally", ], srcs: [ + ":android.hardware.audio.effect-V1-java-source", ":android.hardware.tv.hdmi.connection-V1-java-source", ":android.hardware.tv.hdmi.earc-V1-java-source", ":android.hardware.tv.mediaquality-V1-java-source", diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java index 2930c691c1ac..658ea4c27e4c 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -154,8 +154,6 @@ public class AdbDebuggingManager { @Nullable private final File mUserKeyFile; @Nullable private final File mTempKeysFile; - private static final String WIFI_PERSISTENT_CONFIG_PROPERTY = - "persist.adb.tls_server.enable"; private static final String WIFI_PERSISTENT_GUID = "persist.adb.wifi.guid"; private static final int PAIRING_CODE_LENGTH = 6; @@ -1089,7 +1087,7 @@ public class AdbDebuggingManager { intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mContext.registerReceiver(mBroadcastReceiver, intentFilter); - SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); + SystemProperties.set(AdbService.WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); mConnectionPortPoller = new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener); mConnectionPortPoller.start(); @@ -1139,7 +1137,7 @@ public class AdbDebuggingManager { intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mContext.registerReceiver(mBroadcastReceiver, intentFilter); - SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); + SystemProperties.set(AdbService.WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); mConnectionPortPoller = new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener); mConnectionPortPoller.start(); diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java index 2baccbf9232e..aae48daa5dde 100644 --- a/services/core/java/com/android/server/adb/AdbService.java +++ b/services/core/java/com/android/server/adb/AdbService.java @@ -229,7 +229,7 @@ public class AdbService extends IAdbManager.Stub { * May also contain vendor-specific default functions for testing purposes. */ private static final String USB_PERSISTENT_CONFIG_PROPERTY = "persist.sys.usb.config"; - private static final String WIFI_PERSISTENT_CONFIG_PROPERTY = "persist.adb.tls_server.enable"; + static final String WIFI_PERSISTENT_CONFIG_PROPERTY = "persist.adb.tls_server.enable"; private final Context mContext; private final ContentResolver mContentResolver; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b0b34d0ab9c4..76ba0054583b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -16190,14 +16190,16 @@ public class ActivityManagerService extends IActivityManager.Stub return mUserController.switchUser(targetUserId); } + @Nullable @Override - public String getSwitchingFromUserMessage() { - return mUserController.getSwitchingFromSystemUserMessage(); + public String getSwitchingFromUserMessage(@UserIdInt int userId) { + return mUserController.getSwitchingFromUserMessage(userId); } + @Nullable @Override - public String getSwitchingToUserMessage() { - return mUserController.getSwitchingToSystemUserMessage(); + public String getSwitchingToUserMessage(@UserIdInt int userId) { + return mUserController.getSwitchingToUserMessage(userId); } @Override @@ -16938,13 +16940,13 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage) { - mUserController.setSwitchingFromSystemUserMessage(switchingFromSystemUserMessage); + public void setSwitchingFromUserMessage(@UserIdInt int userId, @Nullable String message) { + mUserController.setSwitchingFromUserMessage(userId, message); } @Override - public void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage) { - mUserController.setSwitchingToSystemUserMessage(switchingToSystemUserMessage); + public void setSwitchingToUserMessage(@UserIdInt int userId, @Nullable String message) { + mUserController.setSwitchingToUserMessage(userId, message); } @Override diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index db0562f5750a..508c01802156 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -810,7 +810,7 @@ class BroadcastProcessQueue { * Return the broadcast being actively dispatched in this process. */ public @NonNull BroadcastRecord getActive() { - return Objects.requireNonNull(mActive); + return Objects.requireNonNull(mActive, toString()); } /** @@ -818,7 +818,7 @@ class BroadcastProcessQueue { * being actively dispatched in this process. */ public int getActiveIndex() { - Objects.requireNonNull(mActive); + Objects.requireNonNull(mActive, toString()); return mActiveIndex; } diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index c76a0d0ac59a..d276b9a94791 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -606,8 +606,9 @@ class BroadcastQueueImpl extends BroadcastQueue { } else { mRunningColdStart.reEnqueueActiveBroadcast(); } - demoteFromRunningLocked(mRunningColdStart); + final BroadcastProcessQueue queue = mRunningColdStart; clearRunningColdStart(); + demoteFromRunningLocked(queue); enqueueUpdateRunningList(); } @@ -1527,6 +1528,15 @@ class BroadcastQueueImpl extends BroadcastQueue { final int cookie = traceBegin("demoteFromRunning"); // We've drained running broadcasts; maybe move back to runnable + if (mRunningColdStart == queue) { + // TODO: b/399020479 - Remove wtf log once we identify the case where mRunningColdStart + // is not getting cleared. + // If this queue is mRunningColdStart, then it should have been cleared before + // it is demoted. Log a wtf if this isn't the case. + Slog.wtf(TAG, "mRunningColdStart has not been cleared; mRunningColdStart.app: " + + mRunningColdStart.app + " , queue.app: " + queue.app, + new IllegalStateException()); + } queue.makeActiveIdle(); queue.traceProcessEnd(); @@ -2332,12 +2342,6 @@ class BroadcastQueueImpl extends BroadcastQueue { @VisibleForTesting @GuardedBy("mService") - @Nullable BroadcastProcessQueue removeProcessQueue(@NonNull ProcessRecord app) { - return removeProcessQueue(app.processName, app.info.uid); - } - - @VisibleForTesting - @GuardedBy("mService") @Nullable BroadcastProcessQueue removeProcessQueue(@NonNull String processName, int uid) { BroadcastProcessQueue prev = null; diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java index 31704c442290..4e1d77c26129 100644 --- a/services/core/java/com/android/server/am/ConnectionRecord.java +++ b/services/core/java/com/android/server/am/ConnectionRecord.java @@ -142,6 +142,10 @@ final class ConnectionRecord implements OomAdjusterModernImpl.Connection{ | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS); } + @Override + public boolean transmitsCpuTime() { + return !hasFlag(Context.BIND_ALLOW_FREEZE); + } public long getFlags() { return flags; @@ -273,6 +277,9 @@ final class ConnectionRecord implements OomAdjusterModernImpl.Connection{ if (hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { sb.append("CAPS "); } + if (hasFlag(Context.BIND_ALLOW_FREEZE)) { + sb.append("!CPU "); + } if (serviceDead) { sb.append("DEAD "); } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 336a35e7a7e3..fa35da30bf4b 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2802,7 +2802,7 @@ public class OomAdjuster { // we check the final procstate, and remove it if the procsate is below BFGS. capability |= getBfslCapabilityFromClient(client); - capability |= getCpuCapabilityFromClient(client); + capability |= getCpuCapabilityFromClient(cr, client); if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) { if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { @@ -3259,7 +3259,7 @@ public class OomAdjuster { // we check the final procstate, and remove it if the procsate is below BFGS. capability |= getBfslCapabilityFromClient(client); - capability |= getCpuCapabilityFromClient(client); + capability |= getCpuCapabilityFromClient(conn, client); if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { // If the other app is cached for any reason, for purposes here @@ -3502,10 +3502,13 @@ public class OomAdjuster { /** * @return the CPU capability from a client (of a service binding or provider). */ - private static int getCpuCapabilityFromClient(ProcessRecord client) { - // Just grant CPU capability every time - // TODO(b/370817323): Populate with reasons to not propagate cpu capability across bindings. - return client.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME; + private static int getCpuCapabilityFromClient(OomAdjusterModernImpl.Connection conn, + ProcessRecord client) { + if (conn == null || conn.transmitsCpuTime()) { + return client.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME; + } else { + return 0; + } } /** diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index 1b7e8f0bd244..7e7b5685cf13 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -635,6 +635,15 @@ public class OomAdjusterModernImpl extends OomAdjuster { * Returns true if this connection can propagate capabilities. */ boolean canAffectCapabilities(); + + /** + * Returns whether this connection transmits PROCESS_CAPABILITY_CPU_TIME to the host, if the + * client possesses it. + */ + default boolean transmitsCpuTime() { + // Always lend this capability by default. + return true; + } } /** diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 18f3500b2d56..40a9bbec3598 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -340,16 +340,16 @@ class UserController implements Handler.Callback { private volatile ArraySet<String> mCurWaitingUserSwitchCallbacks; /** - * Messages for switching from {@link android.os.UserHandle#SYSTEM}. + * Message shown when switching from a user. */ @GuardedBy("mLock") - private String mSwitchingFromSystemUserMessage; + private final SparseArray<String> mSwitchingFromUserMessage = new SparseArray<>(); /** - * Messages for switching to {@link android.os.UserHandle#SYSTEM}. + * Message shown when switching to a user. */ @GuardedBy("mLock") - private String mSwitchingToSystemUserMessage; + private final SparseArray<String> mSwitchingToUserMessage = new SparseArray<>(); /** * Callbacks that are still active after {@link #getUserSwitchTimeoutMs} @@ -2271,8 +2271,8 @@ class UserController implements Handler.Callback { private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) { // The dialog will show and then initiate the user switch by calling startUserInForeground mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second, - getSwitchingFromSystemUserMessageUnchecked(), - getSwitchingToSystemUserMessageUnchecked(), + getSwitchingFromUserMessageUnchecked(fromToUserPair.first.id), + getSwitchingToUserMessageUnchecked(fromToUserPair.second.id), /* onShown= */ () -> sendStartUserSwitchFgMessage(fromToUserPair.second.id)); } @@ -3388,41 +3388,45 @@ class UserController implements Handler.Callback { return mLockPatternUtils.isLockScreenDisabled(userId); } - void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage) { + void setSwitchingFromUserMessage(@UserIdInt int user, @Nullable String message) { synchronized (mLock) { - mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage; + mSwitchingFromUserMessage.put(user, message); } } - void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage) { + void setSwitchingToUserMessage(@UserIdInt int user, @Nullable String message) { synchronized (mLock) { - mSwitchingToSystemUserMessage = switchingToSystemUserMessage; + mSwitchingToUserMessage.put(user, message); } } // Called by AMS, must check permission - String getSwitchingFromSystemUserMessage() { - checkHasManageUsersPermission("getSwitchingFromSystemUserMessage()"); + @Nullable + String getSwitchingFromUserMessage(@UserIdInt int userId) { + checkHasManageUsersPermission("getSwitchingFromUserMessage()"); - return getSwitchingFromSystemUserMessageUnchecked(); + return getSwitchingFromUserMessageUnchecked(userId); } // Called by AMS, must check permission - String getSwitchingToSystemUserMessage() { - checkHasManageUsersPermission("getSwitchingToSystemUserMessage()"); + @Nullable + String getSwitchingToUserMessage(@UserIdInt int userId) { + checkHasManageUsersPermission("getSwitchingToUserMessage()"); - return getSwitchingToSystemUserMessageUnchecked(); + return getSwitchingToUserMessageUnchecked(userId); } - private String getSwitchingFromSystemUserMessageUnchecked() { + @Nullable + private String getSwitchingFromUserMessageUnchecked(@UserIdInt int userId) { synchronized (mLock) { - return mSwitchingFromSystemUserMessage; + return mSwitchingFromUserMessage.get(userId); } } - private String getSwitchingToSystemUserMessageUnchecked() { + @Nullable + private String getSwitchingToUserMessageUnchecked(@UserIdInt int userId) { synchronized (mLock) { - return mSwitchingToSystemUserMessage; + return mSwitchingToUserMessage.get(userId); } } @@ -3518,12 +3522,8 @@ class UserController implements Handler.Callback { + mIsBroadcastSentForSystemUserStarted); pw.println(" mIsBroadcastSentForSystemUserStarting:" + mIsBroadcastSentForSystemUserStarting); - if (mSwitchingFromSystemUserMessage != null) { - pw.println(" mSwitchingFromSystemUserMessage: " + mSwitchingFromSystemUserMessage); - } - if (mSwitchingToSystemUserMessage != null) { - pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage); - } + pw.println(" mSwitchingFromUserMessage:" + mSwitchingFromUserMessage); + pw.println(" mSwitchingToUserMessage:" + mSwitchingToUserMessage); pw.println(" mLastUserUnlockingUptime: " + mLastUserUnlockingUptime); } } @@ -4046,7 +4046,7 @@ class UserController implements Handler.Callback { } void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser, - String switchingFromSystemUserMessage, String switchingToSystemUserMessage, + @Nullable String switchingFromUserMessage, @Nullable String switchingToUserMessage, @NonNull Runnable onShown) { if (mService.mContext.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { @@ -4059,7 +4059,7 @@ class UserController implements Handler.Callback { synchronized (mUserSwitchingDialogLock) { dismissUserSwitchingDialog(null); mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser, - mHandler, switchingFromSystemUserMessage, switchingToSystemUserMessage); + mHandler, switchingFromUserMessage, switchingToUserMessage); mUserSwitchingDialog.show(onShown); } } diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java index 223e0b79ec0b..f4e733a0c99f 100644 --- a/services/core/java/com/android/server/am/UserSwitchingDialog.java +++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java @@ -52,6 +52,7 @@ import com.android.internal.R; import com.android.internal.util.ObjectUtils; import com.android.internal.util.UserIcons; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -75,21 +76,23 @@ class UserSwitchingDialog extends Dialog { protected final UserInfo mOldUser; protected final UserInfo mNewUser; - private final String mSwitchingFromSystemUserMessage; - private final String mSwitchingToSystemUserMessage; + @Nullable + private final String mSwitchingFromUserMessage; + @Nullable + private final String mSwitchingToUserMessage; protected final Context mContext; private final int mTraceCookie; UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser, Handler handler, - String switchingFromSystemUserMessage, String switchingToSystemUserMessage) { + @Nullable String switchingFromUserMessage, @Nullable String switchingToUserMessage) { super(context, R.style.Theme_Material_NoActionBar_Fullscreen); mContext = context; mOldUser = oldUser; mNewUser = newUser; mHandler = handler; - mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage; - mSwitchingToSystemUserMessage = switchingToSystemUserMessage; + mSwitchingFromUserMessage = switchingFromUserMessage; + mSwitchingToUserMessage = switchingToUserMessage; mDisableAnimations = SystemProperties.getBoolean( "debug.usercontroller.disable_user_switching_dialog_animations", false); mTraceCookie = UserHandle.MAX_SECONDARY_USER_ID * oldUser.id + newUser.id; @@ -166,14 +169,14 @@ class UserSwitchingDialog extends Dialog { : R.string.demo_starting_message); } - final String message = - mOldUser.id == UserHandle.USER_SYSTEM ? mSwitchingFromSystemUserMessage - : mNewUser.id == UserHandle.USER_SYSTEM ? mSwitchingToSystemUserMessage : null; + if (mSwitchingFromUserMessage != null || mSwitchingToUserMessage != null) { + if (mSwitchingFromUserMessage != null && mSwitchingToUserMessage != null) { + return mSwitchingFromUserMessage + " " + mSwitchingToUserMessage; + } + return Objects.requireNonNullElse(mSwitchingFromUserMessage, mSwitchingToUserMessage); + } - return message != null ? message - // If switchingFromSystemUserMessage or switchingToSystemUserMessage is null, - // fallback to system message. - : res.getString(R.string.user_switching_message, mNewUser.name); + return res.getString(R.string.user_switching_message, mNewUser.name); } @Override diff --git a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java index 86f5d9bd637f..c53e4bdc2205 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java @@ -189,11 +189,11 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper { @AppOpsManager.HistoricalOpsRequestFilter int requestFilters, int uidFilter, @Nullable String packageNameFilter, @Nullable String attributionTagFilter, IntArray opCodesFilter, int opFlagsFilter, - long beginTime, long endTime, int limit, String orderByColumn) { + long beginTime, long endTime, int limit, String orderByColumn, boolean ascending) { List<SQLCondition> conditions = prepareConditions(beginTime, endTime, requestFilters, uidFilter, packageNameFilter, attributionTagFilter, opCodesFilter, opFlagsFilter); - String sql = buildSql(conditions, orderByColumn, limit); + String sql = buildSql(conditions, orderByColumn, ascending, limit); long startTime = 0; if (Flags.sqliteDiscreteOpEventLoggingEnabled()) { startTime = SystemClock.elapsedRealtime(); @@ -249,7 +249,8 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper { return results; } - private String buildSql(List<SQLCondition> conditions, String orderByColumn, int limit) { + private String buildSql(List<SQLCondition> conditions, String orderByColumn, boolean ascending, + int limit) { StringBuilder sql = new StringBuilder(DiscreteOpsTable.SELECT_TABLE_DATA); if (!conditions.isEmpty()) { sql.append(" WHERE "); @@ -264,6 +265,7 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper { if (orderByColumn != null) { sql.append(" ORDER BY ").append(orderByColumn); + sql.append(ascending ? " ASC " : " DESC "); } if (limit > 0) { sql.append(" LIMIT ").append(limit); diff --git a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java index 4610b6a82e47..70b7016fbb90 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java @@ -31,23 +31,15 @@ import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; import static android.app.AppOpsManager.OP_MONITOR_LOCATION; import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA; import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE; -import static android.app.AppOpsManager.OP_PROCESS_OUTGOING_CALLS; import static android.app.AppOpsManager.OP_READ_DEVICE_IDENTIFIERS; import static android.app.AppOpsManager.OP_READ_HEART_RATE; -import static android.app.AppOpsManager.OP_READ_ICC_SMS; import static android.app.AppOpsManager.OP_READ_OXYGEN_SATURATION; import static android.app.AppOpsManager.OP_READ_SKIN_TEMPERATURE; -import static android.app.AppOpsManager.OP_READ_SMS; import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING; import static android.app.AppOpsManager.OP_RUN_IN_BACKGROUND; -import static android.app.AppOpsManager.OP_SEND_SMS; -import static android.app.AppOpsManager.OP_SMS_FINANCIAL_TRANSACTIONS; -import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; -import static android.app.AppOpsManager.OP_WRITE_ICC_SMS; -import static android.app.AppOpsManager.OP_WRITE_SMS; import static java.lang.Long.min; import static java.lang.Math.max; @@ -134,15 +126,6 @@ abstract class DiscreteOpsRegistry { + "," + OP_READ_HEART_RATE + "," + OP_READ_OXYGEN_SATURATION + "," + OP_READ_SKIN_TEMPERATURE + "," + OP_RESERVED_FOR_TESTING; - static final int[] sDiscreteOpsToLog = - new int[]{OP_FINE_LOCATION, OP_COARSE_LOCATION, OP_EMERGENCY_LOCATION, OP_CAMERA, - OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, OP_PHONE_CALL_CAMERA, - OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_READ_SMS, - OP_WRITE_SMS, OP_SEND_SMS, OP_READ_ICC_SMS, OP_WRITE_ICC_SMS, - OP_SMS_FINANCIAL_TRANSACTIONS, OP_SYSTEM_ALERT_WINDOW, OP_MONITOR_LOCATION, - OP_MONITOR_HIGH_POWER_LOCATION, OP_PROCESS_OUTGOING_CALLS, - }; - static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis(); static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis(); // The duration for which the data is kept, default is 7 days and max 30 days enforced. diff --git a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java index c897891d02c3..0e1fbf3a6d1a 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java @@ -180,7 +180,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { ChronoUnit.MILLIS).toEpochMilli()); List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter, packageNameFilter, attributionTagFilter, opCodes, opFlagsFilter, beginTimeMillis, - endTimeMillis, -1, null); + endTimeMillis, -1, null, false); LongSparseArray<AttributionChain> attributionChains = null; if (assembleChains) { @@ -213,14 +213,15 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps) { - writeAndClearOldAccessHistory(); + // flush the cache into database before dump. + mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear()); IntArray opCodes = new IntArray(); if (dumpOp != AppOpsManager.OP_NONE) { opCodes.add(dumpOp); } List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter, packageNameFilter, attributionTagFilter, opCodes, 0, -1, - -1, nDiscreteOps, DiscreteOpsTable.Columns.ACCESS_TIME); + -1, nDiscreteOps, DiscreteOpsTable.Columns.ACCESS_TIME, false); pw.print(prefix); pw.print("Largest chain id: "); diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 4b5f06b13885..8ef79a916530 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1472,8 +1472,8 @@ public class AudioDeviceBroker { mAudioService.postAccessoryPlugMediaUnmute(device); } - /*package*/ int getVolumeForDeviceIgnoreMute(int streamType, int device) { - return mAudioService.getVolumeForDeviceIgnoreMute(streamType, device); + /*package*/ int getVssVolumeForDevice(int streamType, int device) { + return mAudioService.getVssVolumeForDevice(streamType, device); } /*package*/ int getMaxVssVolumeForStream(int streamType) { diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 2e6d98485e85..829d9ea7495f 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -2482,7 +2482,7 @@ public class AudioDeviceInventory { @GuardedBy("mDevicesLock") private void makeHearingAidDeviceAvailable( String address, String name, int streamType, String eventSource) { - final int hearingAidVolIndex = mDeviceBroker.getVolumeForDeviceIgnoreMute(streamType, + final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType, DEVICE_OUT_HEARING_AID); mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType); @@ -2672,7 +2672,7 @@ public class AudioDeviceInventory { } final int leAudioVolIndex = (volumeIndex == -1) - ? mDeviceBroker.getVolumeForDeviceIgnoreMute(streamType, device) + ? mDeviceBroker.getVssVolumeForDevice(streamType, device) : volumeIndex; final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType); mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index a43e4d98c077..766456134b20 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -529,7 +529,7 @@ public class AudioService extends IAudioService.Stub */ private InputDeviceVolumeHelper mInputDeviceVolumeHelper; - /*package*/ int getVolumeForDeviceIgnoreMute(int stream, int device) { + /*package*/ int getVssVolumeForDevice(int stream, int device) { final VolumeStreamState streamState = mStreamStates.get(stream); return streamState != null ? streamState.getIndex(device) : -1; } @@ -4997,6 +4997,8 @@ public class AudioService extends IAudioService.Stub pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:" + disablePrescaleAbsoluteVolume()); pw.println("\tcom.android.media.audio.setStreamVolumeOrder - EOL"); + pw.println("\tandroid.media.audio.ringtoneUserUriCheck:" + + android.media.audio.Flags.ringtoneUserUriCheck()); pw.println("\tandroid.media.audio.roForegroundAudioControl:" + roForegroundAudioControl()); pw.println("\tandroid.media.audio.scoManagedByAudio:" @@ -5098,7 +5100,7 @@ public class AudioService extends IAudioService.Stub } final int device = absVolumeDevices.toArray(new Integer[0])[0].intValue(); - final int index = getVolumeForDeviceIgnoreMute(streamType, device); + final int index = getStreamVolume(streamType, device); if (DEBUG_VOL) { Slog.i(TAG, "onUpdateContextualVolumes streamType: " + streamType diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 67afff79dffd..643f3308d8f5 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -724,7 +724,7 @@ public class SoundDoseHelper { int device = mAudioService.getDeviceForStream(AudioSystem.STREAM_MUSIC); if (safeDevicesContains(device) && isStreamActive) { scheduleMusicActiveCheck(); - int index = mAudioService.getVolumeForDeviceIgnoreMute(AudioSystem.STREAM_MUSIC, + int index = mAudioService.getVssVolumeForDevice(AudioSystem.STREAM_MUSIC, device); if (index > safeMediaVolumeIndex(device)) { // Approximate cumulative active music time diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 97f9a7c4f2b0..8f5b831ca0b4 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -219,7 +219,9 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { && reason != HdmiControlService.INITIATED_BY_BOOT_UP; List<HdmiCecMessage> bufferedActiveSource = mDelayedMessageBuffer .getBufferedMessagesWithOpcode(Constants.MESSAGE_ACTIVE_SOURCE); - if (bufferedActiveSource.isEmpty()) { + List<HdmiCecMessage> bufferedActiveSourceFromService = mService.getCecMessageWithOpcode( + Constants.MESSAGE_ACTIVE_SOURCE); + if (bufferedActiveSource.isEmpty() && bufferedActiveSourceFromService.isEmpty()) { addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() { @Override public void onComplete(int result) { diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 6d973ac8d1b5..fdd0ef2f90e1 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1593,6 +1593,17 @@ public class HdmiControlService extends SystemService { this.mCecMessageBuffer = cecMessageBuffer; } + List<HdmiCecMessage> getCecMessageWithOpcode(int opcode) { + List<HdmiCecMessage> cecMessagesWithOpcode = new ArrayList<>(); + List<HdmiCecMessage> cecMessages = mCecMessageBuffer.getBuffer(); + for (HdmiCecMessage message: cecMessages) { + if (message.getOpcode() == opcode) { + cecMessagesWithOpcode.add(message); + } + } + return cecMessagesWithOpcode; + } + /** * Returns {@link Looper} of main thread. Use this {@link Looper} instance * for tasks that are running on main service thread. diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index c2fecf283a34..d9db178e0dc2 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -568,6 +568,7 @@ public class InputManagerService extends IInputManager.Stub } mWindowManagerCallbacks = callbacks; registerLidSwitchCallbackInternal(mWindowManagerCallbacks); + mKeyGestureController.setWindowManagerCallbacks(callbacks); } public void setWiredAccessoryCallbacks(WiredAccessoryCallbacks callbacks) { @@ -2756,24 +2757,6 @@ public class InputManagerService extends IInputManager.Stub @Nullable IBinder focussedToken) { return InputManagerService.this.handleKeyGestureEvent(event); } - - @Override - public boolean isKeyGestureSupported(int gestureType) { - switch (gestureType) { - case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP: - case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN: - case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS: - return true; - default: - return false; - - } - } }); } @@ -3371,6 +3354,11 @@ public class InputManagerService extends IInputManager.Stub */ @Nullable SurfaceControl createSurfaceForGestureMonitor(String name, int displayId); + + /** + * Provide information on whether the keyguard is currently locked or not. + */ + boolean isKeyguardLocked(int displayId); } /** diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java index ef5babf19d83..395c77322c04 100644 --- a/services/core/java/com/android/server/input/KeyGestureController.java +++ b/services/core/java/com/android/server/input/KeyGestureController.java @@ -62,8 +62,10 @@ import android.view.Display; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; +import android.view.ViewConfiguration; import com.android.internal.R; +import com.android.internal.accessibility.AccessibilityShortcutController; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.IShortcutService; @@ -104,6 +106,7 @@ final class KeyGestureController { private static final int MSG_NOTIFY_KEY_GESTURE_EVENT = 1; private static final int MSG_PERSIST_CUSTOM_GESTURES = 2; private static final int MSG_LOAD_CUSTOM_GESTURES = 3; + private static final int MSG_ACCESSIBILITY_SHORTCUT = 4; // must match: config_settingsKeyBehavior in config.xml private static final int SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0; @@ -122,12 +125,15 @@ final class KeyGestureController { static final int POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS = 2; private final Context mContext; + private InputManagerService.WindowManagerCallbacks mWindowManagerCallbacks; private final Handler mHandler; private final Handler mIoHandler; private final int mSystemPid; private final KeyCombinationManager mKeyCombinationManager; private final SettingsObserver mSettingsObserver; private final AppLaunchShortcutManager mAppLaunchShortcutManager; + @VisibleForTesting + final AccessibilityShortcutController mAccessibilityShortcutController; private final InputGestureManager mInputGestureManager; private final DisplayManager mDisplayManager; @GuardedBy("mInputDataStore") @@ -175,8 +181,14 @@ final class KeyGestureController { private final boolean mVisibleBackgroundUsersEnabled = isVisibleBackgroundUsersEnabled(); - KeyGestureController(Context context, Looper looper, Looper ioLooper, + public KeyGestureController(Context context, Looper looper, Looper ioLooper, InputDataStore inputDataStore) { + this(context, looper, ioLooper, inputDataStore, new Injector()); + } + + @VisibleForTesting + KeyGestureController(Context context, Looper looper, Looper ioLooper, + InputDataStore inputDataStore, Injector injector) { mContext = context; mHandler = new Handler(looper, this::handleMessage); mIoHandler = new Handler(ioLooper, this::handleIoMessage); @@ -197,6 +209,8 @@ final class KeyGestureController { mSettingsObserver = new SettingsObserver(mHandler); mAppLaunchShortcutManager = new AppLaunchShortcutManager(mContext); mInputGestureManager = new InputGestureManager(mContext); + mAccessibilityShortcutController = injector.getAccessibilityShortcutController(mContext, + mHandler); mDisplayManager = Objects.requireNonNull(mContext.getSystemService(DisplayManager.class)); mInputDataStore = inputDataStore; mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); @@ -295,8 +309,8 @@ final class KeyGestureController { KeyEvent.KEYCODE_VOLUME_UP) { @Override public boolean preCondition() { - return isKeyGestureSupported( - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD); + return mAccessibilityShortcutController.isAccessibilityShortcutAvailable( + mWindowManagerCallbacks.isKeyguardLocked(DEFAULT_DISPLAY)); } @Override @@ -376,15 +390,15 @@ final class KeyGestureController { KeyEvent.KEYCODE_DPAD_DOWN) { @Override public boolean preCondition() { - return isKeyGestureSupported( - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD); + return mAccessibilityShortcutController + .isAccessibilityShortcutAvailable(false); } @Override public void execute() { handleMultiKeyGesture( new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN}, - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD, KeyGestureEvent.ACTION_GESTURE_START, 0); } @@ -392,7 +406,7 @@ final class KeyGestureController { public void cancel() { handleMultiKeyGesture( new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN}, - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD, KeyGestureEvent.ACTION_GESTURE_COMPLETE, KeyGestureEvent.FLAG_CANCELLED); } @@ -438,6 +452,7 @@ final class KeyGestureController { mSettingsObserver.observe(); mAppLaunchShortcutManager.systemRunning(); mInputGestureManager.systemRunning(); + initKeyGestures(); int userId; synchronized (mUserLock) { @@ -447,6 +462,27 @@ final class KeyGestureController { mIoHandler.obtainMessage(MSG_LOAD_CUSTOM_GESTURES, userId).sendToTarget(); } + @SuppressLint("MissingPermission") + private void initKeyGestures() { + InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class)); + im.registerKeyGestureEventHandler((event, focusedToken) -> { + switch (event.getKeyGestureType()) { + case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD: + if (event.getAction() == KeyGestureEvent.ACTION_GESTURE_START) { + mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT), + getAccessibilityShortcutTimeout()); + } else { + mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT); + } + return true; + default: + return false; + } + }); + } + public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { if (mVisibleBackgroundUsersEnabled && shouldIgnoreKeyEventForVisibleBackgroundUser(event)) { return false; @@ -971,17 +1007,6 @@ final class KeyGestureController { return false; } - private boolean isKeyGestureSupported(@KeyGestureEvent.KeyGestureType int gestureType) { - synchronized (mKeyGestureHandlerRecords) { - for (KeyGestureHandlerRecord handler : mKeyGestureHandlerRecords.values()) { - if (handler.isKeyGestureSupported(gestureType)) { - return true; - } - } - } - return false; - } - public void notifyKeyGestureCompleted(int deviceId, int[] keycodes, int modifierState, @KeyGestureEvent.KeyGestureType int gestureType) { // TODO(b/358569822): Once we move the gesture detection logic to IMS, we ideally @@ -1019,9 +1044,16 @@ final class KeyGestureController { synchronized (mUserLock) { mCurrentUserId = userId; } + mAccessibilityShortcutController.setCurrentUser(userId); mIoHandler.obtainMessage(MSG_LOAD_CUSTOM_GESTURES, userId).sendToTarget(); } + + public void setWindowManagerCallbacks( + @NonNull InputManagerService.WindowManagerCallbacks callbacks) { + mWindowManagerCallbacks = callbacks; + } + private boolean isDefaultDisplayOn() { Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); if (defaultDisplay == null) { @@ -1068,6 +1100,9 @@ final class KeyGestureController { AidlKeyGestureEvent event = (AidlKeyGestureEvent) msg.obj; notifyKeyGestureEvent(event); break; + case MSG_ACCESSIBILITY_SHORTCUT: + mAccessibilityShortcutController.performAccessibilityShortcut(); + break; } return true; } @@ -1347,17 +1382,6 @@ final class KeyGestureController { } return false; } - - public boolean isKeyGestureSupported(@KeyGestureEvent.KeyGestureType int gestureType) { - try { - return mKeyGestureHandler.isKeyGestureSupported(gestureType); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to identify if key gesture type is supported by the " - + "process " + mPid + ", assuming it died.", ex); - binderDied(); - } - return false; - } } private class SettingsObserver extends ContentObserver { @@ -1413,6 +1437,25 @@ final class KeyGestureController { return event; } + private long getAccessibilityShortcutTimeout() { + synchronized (mUserLock) { + final ViewConfiguration config = ViewConfiguration.get(mContext); + final boolean hasDialogShown = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, mCurrentUserId) != 0; + final boolean skipTimeoutRestriction = + Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.SKIP_ACCESSIBILITY_SHORTCUT_DIALOG_TIMEOUT_RESTRICTION, + 0, mCurrentUserId) != 0; + + // If users manually set the volume key shortcut for any accessibility service, the + // system would bypass the timeout restriction of the shortcut dialog. + return hasDialogShown || skipTimeoutRestriction + ? config.getAccessibilityShortcutKeyTimeoutAfterConfirmation() + : config.getAccessibilityShortcutKeyTimeout(); + } + } + public void dump(IndentingPrintWriter ipw) { ipw.println("KeyGestureController:"); ipw.increaseIndent(); @@ -1459,4 +1502,12 @@ final class KeyGestureController { mAppLaunchShortcutManager.dump(ipw); mInputGestureManager.dump(ipw); } + + @VisibleForTesting + static class Injector { + AccessibilityShortcutController getAccessibilityShortcutController(Context context, + Handler handler) { + return new AccessibilityShortcutController(context, handler, UserHandle.USER_SYSTEM); + } + } } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index f137de1b3e1d..988924d9f498 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -25,6 +25,7 @@ import static android.media.MediaRouter2.SCANNING_STATE_SCANNING_FULL; import static android.media.MediaRouter2.SCANNING_STATE_WHILE_INTERACTIVE; import static android.media.MediaRouter2Utils.getOriginalId; import static android.media.MediaRouter2Utils.getProviderId; + import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION; import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_DESELECT_ROUTE; @@ -63,6 +64,7 @@ import android.media.MediaRouter2Manager; import android.media.RouteDiscoveryPreference; import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; +import android.media.SuggestedDeviceInfo; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -76,18 +78,21 @@ import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.util.SparseArray; + import com.android.internal.annotations.GuardedBy; import com.android.internal.util.function.pooled.PooledLambda; import com.android.media.flags.Flags; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; import com.android.server.statusbar.StatusBarManagerInternal; + import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -551,6 +556,36 @@ class MediaRouter2ServiceImpl { } } + public void setDeviceSuggestionsWithRouter2( + @NonNull IMediaRouter2 router, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + Objects.requireNonNull(router, "router must not be null"); + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + setDeviceSuggestionsWithRouter2Locked(router, suggestedDeviceInfo); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Nullable + public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithRouter2( + @NonNull IMediaRouter2 router) { + Objects.requireNonNull(router, "router must not be null"); + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + return getDeviceSuggestionsWithRouter2Locked(router); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + // End of methods that implement MediaRouter2 operations. // Start of methods that implement MediaRouter2Manager operations. @@ -805,6 +840,36 @@ class MediaRouter2ServiceImpl { } } + public void setDeviceSuggestionsWithManager( + @NonNull IMediaRouter2Manager manager, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + Objects.requireNonNull(manager, "manager must not be null"); + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + setDeviceSuggestionsWithManagerLocked(manager, suggestedDeviceInfo); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Nullable + public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithManager( + @NonNull IMediaRouter2Manager manager) { + Objects.requireNonNull(manager, "manager must not be null"); + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + return getDeviceSuggestionsWithManagerLocked(manager); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) public boolean showMediaOutputSwitcherWithProxyRouter( @NonNull IMediaRouter2Manager proxyRouter) { @@ -1582,6 +1647,61 @@ class MediaRouter2ServiceImpl { DUMMY_REQUEST_ID, routerRecord, uniqueSessionId)); } + @GuardedBy("mLock") + private void setDeviceSuggestionsWithRouter2Locked( + @NonNull IMediaRouter2 router, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + final IBinder binder = router.asBinder(); + final RouterRecord routerRecord = mAllRouterRecords.get(binder); + + if (routerRecord == null) { + Slog.w( + TAG, + TextUtils.formatSimple( + "Ignoring set device suggestion for unknown router: %s", router)); + return; + } + + Slog.i( + TAG, + TextUtils.formatSimple( + "setDeviceSuggestions | router: %d suggestion: %d", + routerRecord.mPackageName, suggestedDeviceInfo)); + + routerRecord.mUserRecord.updateDeviceSuggestionsLocked( + routerRecord.mPackageName, routerRecord.mPackageName, suggestedDeviceInfo); + routerRecord.mUserRecord.mHandler.sendMessage( + obtainMessage( + UserHandler::notifyDeviceSuggestionsUpdatedOnHandler, + routerRecord.mUserRecord.mHandler, + routerRecord.mPackageName, + routerRecord.mPackageName, + suggestedDeviceInfo)); + } + + @GuardedBy("mLock") + @Nullable + private Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithRouter2Locked( + @NonNull IMediaRouter2 router) { + final IBinder binder = router.asBinder(); + final RouterRecord routerRecord = mAllRouterRecords.get(binder); + + if (routerRecord == null) { + Slog.w( + TAG, + TextUtils.formatSimple( + "Attempted to get device suggestion for unknown router: %s", router)); + return null; + } + + Slog.i( + TAG, + TextUtils.formatSimple( + "getDeviceSuggestions | router: %d", routerRecord.mPackageName)); + + return routerRecord.mUserRecord.getDeviceSuggestionsLocked(routerRecord.mPackageName); + } + // End of locked methods that are used by MediaRouter2. // Start of locked methods that are used by MediaRouter2Manager. @@ -1972,6 +2092,68 @@ class MediaRouter2ServiceImpl { uniqueRequestId, routerRecord, uniqueSessionId)); } + @GuardedBy("mLock") + private void setDeviceSuggestionsWithManagerLocked( + @NonNull IMediaRouter2Manager manager, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + final IBinder binder = manager.asBinder(); + ManagerRecord managerRecord = mAllManagerRecords.get(binder); + + if (managerRecord == null || managerRecord.mTargetPackageName == null) { + Slog.w( + TAG, + TextUtils.formatSimple( + "Ignoring set device suggestion for unknown manager: %s", manager)); + return; + } + + Slog.i( + TAG, + TextUtils.formatSimple( + "setDeviceSuggestions | manager: %d, suggestingPackageName: %d suggestion:" + + " %d", + managerRecord.mManagerId, + managerRecord.mOwnerPackageName, + suggestedDeviceInfo)); + + managerRecord.mUserRecord.updateDeviceSuggestionsLocked( + managerRecord.mTargetPackageName, + managerRecord.mOwnerPackageName, + suggestedDeviceInfo); + managerRecord.mUserRecord.mHandler.sendMessage( + obtainMessage( + UserHandler::notifyDeviceSuggestionsUpdatedOnHandler, + managerRecord.mUserRecord.mHandler, + managerRecord.mTargetPackageName, + managerRecord.mOwnerPackageName, + suggestedDeviceInfo)); + } + + @GuardedBy("mLock") + @Nullable + private Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithManagerLocked( + @NonNull IMediaRouter2Manager manager) { + final IBinder binder = manager.asBinder(); + ManagerRecord managerRecord = mAllManagerRecords.get(binder); + + if (managerRecord == null || managerRecord.mTargetPackageName == null) { + Slog.w( + TAG, + TextUtils.formatSimple( + "Attempted to get device suggestion for unknown manager: %s", manager)); + return null; + } + + Slog.i( + TAG, + TextUtils.formatSimple( + "getDeviceSuggestionsWithManagerLocked | manager: %d", + managerRecord.mManagerId)); + + return managerRecord.mUserRecord.getDeviceSuggestionsLocked( + managerRecord.mTargetPackageName); + } + // End of locked methods that are used by MediaRouter2Manager. // Start of locked methods that are used by both MediaRouter2 and MediaRouter2Manager. @@ -2047,6 +2229,11 @@ class MediaRouter2ServiceImpl { //TODO: make records private for thread-safety final ArrayList<RouterRecord> mRouterRecords = new ArrayList<>(); final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>(); + + // @GuardedBy("mLock") + private final Map<String, Map<String, List<SuggestedDeviceInfo>>> mDeviceSuggestions = + new HashMap<>(); + RouteDiscoveryPreference mCompositeDiscoveryPreference = RouteDiscoveryPreference.EMPTY; Set<String> mActivelyScanningPackages = Set.of(); final UserHandler mHandler; @@ -2076,6 +2263,25 @@ class MediaRouter2ServiceImpl { return null; } + // @GuardedBy("mLock") + public void updateDeviceSuggestionsLocked( + String packageName, + String suggestingPackageName, + List<SuggestedDeviceInfo> deviceSuggestions) { + mDeviceSuggestions.putIfAbsent( + packageName, new HashMap<String, List<SuggestedDeviceInfo>>()); + Map<String, List<SuggestedDeviceInfo>> suggestions = + mDeviceSuggestions.get(packageName); + suggestions.put(suggestingPackageName, deviceSuggestions); + } + + // @GuardedBy("mLock") + @Nullable + public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsLocked( + String packageName) { + return mDeviceSuggestions.get(packageName); + } + public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { pw.println(prefix + "UserRecord"); @@ -2314,6 +2520,15 @@ class MediaRouter2ServiceImpl { } } + public void notifyDeviceSuggestionsUpdated( + String suggestingPackageName, List<SuggestedDeviceInfo> suggestedDeviceInfo) { + try { + mRouter.notifyDeviceSuggestionsUpdated(suggestingPackageName, suggestedDeviceInfo); + } catch (RemoteException ex) { + logRemoteException("notifyDeviceSuggestionsUpdated", ex); + } + } + /** * Sends the corresponding router a {@link RoutingSessionInfo session} creation request, * with the given {@link MediaRoute2Info} as the initial member. @@ -3556,6 +3771,41 @@ class MediaRouter2ServiceImpl { // need to update routers other than the one making the update. } + private void notifyDeviceSuggestionsUpdatedOnHandler( + String routerPackageName, + String suggestingPackageName, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + MediaRouter2ServiceImpl service = mServiceRef.get(); + if (service == null) { + return; + } + List<IMediaRouter2Manager> managers = new ArrayList<>(); + synchronized (service.mLock) { + for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) { + if (TextUtils.equals(managerRecord.mTargetPackageName, routerPackageName)) { + managers.add(managerRecord.mManager); + } + } + for (IMediaRouter2Manager manager : managers) { + try { + manager.notifyDeviceSuggestionsUpdated( + routerPackageName, suggestingPackageName, suggestedDeviceInfo); + } catch (RemoteException ex) { + Slog.w( + TAG, + "Failed to notify suggesteion changed. Manager probably died.", + ex); + } + } + for (RouterRecord routerRecord : mUserRecord.mRouterRecords) { + if (TextUtils.equals(routerRecord.mPackageName, routerPackageName)) { + routerRecord.notifyDeviceSuggestionsUpdated( + suggestingPackageName, suggestedDeviceInfo); + } + } + } + } + private void updateDiscoveryPreferenceOnHandler() { MediaRouter2ServiceImpl service = mServiceRef.get(); if (service == null) { diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 35bb19943a24..11f449e790a8 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -49,6 +49,7 @@ import android.media.RemoteDisplayState.RemoteDisplayInfo; import android.media.RouteDiscoveryPreference; import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; +import android.media.SuggestedDeviceInfo; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -80,6 +81,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -526,6 +528,21 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override + public void setDeviceSuggestionsWithRouter2( + IMediaRouter2 router, @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + mService2.setDeviceSuggestionsWithRouter2(router, suggestedDeviceInfo); + } + + // Binder call + @Override + @Nullable + public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithRouter2( + IMediaRouter2 router) { + return mService2.getDeviceSuggestionsWithRouter2(router); + } + + // Binder call + @Override public List<RoutingSessionInfo> getRemoteSessions(IMediaRouter2Manager manager) { return mService2.getRemoteSessions(manager); } @@ -666,6 +683,22 @@ public final class MediaRouterService extends IMediaRouterService.Stub return mService2.showMediaOutputSwitcherWithProxyRouter(proxyRouter); } + // Binder call + @Override + public void setDeviceSuggestionsWithManager( + @NonNull IMediaRouter2Manager manager, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + mService2.setDeviceSuggestionsWithManager(manager, suggestedDeviceInfo); + } + + // Binder call + @Override + @Nullable + public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithManager( + IMediaRouter2Manager manager) { + return mService2.getDeviceSuggestionsWithManager(manager); + } + void restoreBluetoothA2dp() { try { boolean a2dpOn; 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 9e38435ff7f1..ad108f64ffe3 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -28,6 +28,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.hardware.audio.effect.DefaultExtension; import android.hardware.tv.mediaquality.AmbientBacklightColorFormat; import android.hardware.tv.mediaquality.IMediaQuality; import android.hardware.tv.mediaquality.IPictureProfileAdjustmentListener; @@ -57,6 +58,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Environment; import android.os.IBinder; +import android.os.Parcel; import android.os.PersistableBundle; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -365,13 +367,21 @@ public class MediaQualityService extends SystemService { try { if (mMediaQuality != null) { + PictureParameters pp = new PictureParameters(); PictureParameter[] pictureParameters = MediaQualityUtils .convertPersistableBundleToPictureParameterList(params); - PictureParameters pp = new PictureParameters(); + PersistableBundle vendorPictureParameters = params + .getPersistableBundle(BaseParameters.VENDOR_PARAMETERS); + Parcel parcel = Parcel.obtain(); + if (vendorPictureParameters != null) { + setVendorPictureParameters(pp, parcel, vendorPictureParameters); + } + pp.pictureParameters = pictureParameters; mMediaQuality.sendDefaultPictureParameters(pp); + parcel.recycle(); return true; } } catch (RemoteException e) { @@ -1419,11 +1429,19 @@ public class MediaQualityService extends SystemService { MediaQualityUtils.convertPersistableBundleToPictureParameterList( params); + PersistableBundle vendorPictureParameters = params + .getPersistableBundle(BaseParameters.VENDOR_PARAMETERS); + Parcel parcel = Parcel.obtain(); + if (vendorPictureParameters != null) { + setVendorPictureParameters(pictureParameters, parcel, vendorPictureParameters); + } + android.hardware.tv.mediaquality.PictureProfile toReturn = new android.hardware.tv.mediaquality.PictureProfile(); toReturn.pictureProfileId = id; toReturn.parameters = pictureParameters; + parcel.recycle(); return toReturn; } @@ -1729,4 +1747,16 @@ public class MediaQualityService extends SystemService { return android.hardware.tv.mediaquality.IMediaQualityCallback.Stub.VERSION; } } + + private void setVendorPictureParameters( + PictureParameters pictureParameters, + Parcel parcel, + PersistableBundle vendorPictureParameters) { + vendorPictureParameters.writeToParcel(parcel, 0); + byte[] vendorBundleToByteArray = parcel.marshall(); + DefaultExtension defaultExtension = new DefaultExtension(); + defaultExtension.bytes = Arrays.copyOf( + vendorBundleToByteArray, vendorBundleToByteArray.length); + pictureParameters.vendorPictureParameters.setParcelable(defaultExtension); + } } diff --git a/services/core/java/com/android/server/os/instrumentation/OWNERS b/services/core/java/com/android/server/os/instrumentation/OWNERS new file mode 100644 index 000000000000..2522426d93f8 --- /dev/null +++ b/services/core/java/com/android/server/os/instrumentation/OWNERS @@ -0,0 +1 @@ +include platform/packages/modules/UprobeStats:/OWNERS
\ No newline at end of file diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 46dc75817a36..3230e891db55 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -4240,66 +4240,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (!useKeyGestureEventHandler()) { return; } - mInputManager.registerKeyGestureEventHandler(new InputManager.KeyGestureEventHandler() { - @Override - public boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event, - @Nullable IBinder focusedToken) { - boolean handled = PhoneWindowManager.this.handleKeyGestureEvent(event, - focusedToken); - if (handled && !event.isCancelled() && Arrays.stream(event.getKeycodes()).anyMatch( - (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) { - mPowerKeyHandled = true; - } - return handled; - } - - @Override - public boolean isKeyGestureSupported(int gestureType) { - switch (gestureType) { - case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS: - case KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH: - case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT: - case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT: - case KeyGestureEvent.KEY_GESTURE_TYPE_HOME: - case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS: - case KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL: - case KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT: - case KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT: - case KeyGestureEvent.KEY_GESTURE_TYPE_BACK: - case KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION: - case KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE: - case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT: - case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT: - case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER: - case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP: - case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN: - case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER: - case KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS: - case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS: - case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH: - case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH: - case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT: - case KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS: - case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB: - case KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD: - case KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD: - case KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS: - case KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS: - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD: - return mAccessibilityShortcutController.isAccessibilityShortcutAvailable( - isKeyguardLocked()); - case KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD: - return mAccessibilityShortcutController.isAccessibilityShortcutAvailable( - false); - default: - return false; - } + mInputManager.registerKeyGestureEventHandler((event, focusedToken) -> { + boolean handled = PhoneWindowManager.this.handleKeyGestureEvent(event, + focusedToken); + if (handled && !event.isCancelled() && Arrays.stream(event.getKeycodes()).anyMatch( + (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) { + mPowerKeyHandled = true; } + return handled; }); } @@ -4457,13 +4405,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { cancelPendingScreenshotChordAction(); } return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD: - if (start) { - interceptAccessibilityShortcutChord(); - } else { - cancelPendingAccessibilityShortcutAction(); - } - return true; case KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD: if (start) { interceptRingerToggleChord(); @@ -4481,14 +4422,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { cancelGlobalActionsAction(); } return true; - // TODO (b/358569822): Consolidate TV and non-TV gestures into same KeyGestureEvent - case KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD: - if (start) { - interceptAccessibilityGestureTv(); - } else { - cancelAccessibilityGestureTv(); - } - return true; case KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT: if (start) { interceptBugreportGestureTv(); diff --git a/services/core/java/com/android/server/updates/CertPinInstallReceiver.java b/services/core/java/com/android/server/updates/CertPinInstallReceiver.java index c8e7a8dea5c3..250e99b47b1a 100644 --- a/services/core/java/com/android/server/updates/CertPinInstallReceiver.java +++ b/services/core/java/com/android/server/updates/CertPinInstallReceiver.java @@ -19,10 +19,7 @@ package com.android.server.updates; import android.content.Context; import android.content.Intent; -import java.io.File; - public class CertPinInstallReceiver extends ConfigUpdateInstallReceiver { - private static final String KEYCHAIN_DIR = "/data/misc/keychain/"; public CertPinInstallReceiver() { super("/data/misc/keychain/", "pins", "metadata/", "version"); @@ -30,22 +27,7 @@ public class CertPinInstallReceiver extends ConfigUpdateInstallReceiver { @Override public void onReceive(final Context context, final Intent intent) { - if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { - if (com.android.server.flags.Flags.certpininstallerRemoval()) { - File pins = new File(KEYCHAIN_DIR + "pins"); - if (pins.exists()) { - pins.delete(); - } - File version = new File(KEYCHAIN_DIR + "metadata/version"); - if (version.exists()) { - version.delete(); - } - File metadata = new File(KEYCHAIN_DIR + "metadata"); - if (metadata.exists()) { - metadata.delete(); - } - } - } else if (!com.android.server.flags.Flags.certpininstallerRemoval()) { + if (!com.android.server.flags.Flags.certpininstallerRemoval()) { super.onReceive(context, intent); } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java index 8e8455ad5288..6e640d890fb8 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java @@ -17,7 +17,6 @@ package com.android.server.wallpaper; import static android.app.WallpaperManager.ORIENTATION_LANDSCAPE; -import static android.app.WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE; import static android.app.WallpaperManager.ORIENTATION_UNKNOWN; import static android.app.WallpaperManager.getOrientation; import static android.app.WallpaperManager.getRotatedOrientation; @@ -85,20 +84,11 @@ public class WallpaperCropper { private final WallpaperDisplayHelper mWallpaperDisplayHelper; - /** - * Helpers exposed to the window manager part (WallpaperController) - */ - public interface WallpaperCropUtils { - - /** - * Equivalent to {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)} - */ - Rect getCrop(Point displaySize, Point bitmapSize, - SparseArray<Rect> suggestedCrops, boolean rtl); - } + private final WallpaperDefaultDisplayInfo mDefaultDisplayInfo; WallpaperCropper(WallpaperDisplayHelper wallpaperDisplayHelper) { mWallpaperDisplayHelper = wallpaperDisplayHelper; + mDefaultDisplayInfo = mWallpaperDisplayHelper.getDefaultDisplayInfo(); } /** @@ -116,16 +106,16 @@ public class WallpaperCropper { * {@link #getAdjustedCrop}. * </ul> * - * @param displaySize The dimensions of the surface where we want to render the wallpaper - * @param bitmapSize The dimensions of the wallpaper bitmap - * @param rtl Whether the device is right-to-left - * @param suggestedCrops An optional list of user-defined crops for some orientations. - * If there is a suggested crop for + * @param displaySize The dimensions of the surface where we want to render the wallpaper + * @param defaultDisplayInfo The default display info + * @param bitmapSize The dimensions of the wallpaper bitmap + * @param rtl Whether the device is right-to-left + * @param suggestedCrops An optional list of user-defined crops for some orientations. * * @return A Rect indicating how to crop the bitmap for the current display. */ - public Rect getCrop(Point displaySize, Point bitmapSize, - SparseArray<Rect> suggestedCrops, boolean rtl) { + public static Rect getCrop(Point displaySize, WallpaperDefaultDisplayInfo defaultDisplayInfo, + Point bitmapSize, SparseArray<Rect> suggestedCrops, boolean rtl) { int orientation = getOrientation(displaySize); @@ -135,23 +125,24 @@ public class WallpaperCropper { // The first exception is if the device is a foldable and we're on the folded screen. // In that case, show the center of what's on the unfolded screen. - int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation); + int unfoldedOrientation = defaultDisplayInfo.getUnfoldedOrientation(orientation); if (unfoldedOrientation != ORIENTATION_UNKNOWN) { // Let the system know that we're showing the full image on the unfolded screen SparseArray<Rect> newSuggestedCrops = new SparseArray<>(); newSuggestedCrops.put(unfoldedOrientation, crop); // This will fall into "Case 4" of this function and center the folded screen - return getCrop(displaySize, bitmapSize, newSuggestedCrops, rtl); + return getCrop(displaySize, defaultDisplayInfo, bitmapSize, newSuggestedCrops, + rtl); } // The second exception is if we're on tablet and we're on portrait mode. // In that case, center the wallpaper relatively to landscape and put some parallax. - boolean isTablet = mWallpaperDisplayHelper.isLargeScreen() - && !mWallpaperDisplayHelper.isFoldable(); + boolean isTablet = defaultDisplayInfo.isLargeScreen && !defaultDisplayInfo.isFoldable; if (isTablet && displaySize.x < displaySize.y) { Point rotatedDisplaySize = new Point(displaySize.y, displaySize.x); // compute the crop on landscape (without parallax) - Rect landscapeCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl); + Rect landscapeCrop = getCrop(rotatedDisplaySize, defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl); landscapeCrop = noParallax(landscapeCrop, rotatedDisplaySize, bitmapSize, rtl); // compute the crop on portrait at the center of the landscape crop crop = getAdjustedCrop(landscapeCrop, bitmapSize, displaySize, false, rtl, ADD); @@ -173,7 +164,8 @@ public class WallpaperCropper { if (testCrop == null || testCrop.left < 0 || testCrop.top < 0 || testCrop.right > bitmapSize.x || testCrop.bottom > bitmapSize.y) { Slog.w(TAG, "invalid crop: " + testCrop + " for bitmap size: " + bitmapSize); - return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl); + return getCrop(displaySize, defaultDisplayInfo, bitmapSize, new SparseArray<>(), + rtl); } } @@ -185,10 +177,9 @@ public class WallpaperCropper { // Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and // trying to preserve the zoom level and the center of the image - SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes(); int rotatedOrientation = getRotatedOrientation(orientation); suggestedCrop = suggestedCrops.get(rotatedOrientation); - Point suggestedDisplaySize = defaultDisplaySizes.get(rotatedOrientation); + Point suggestedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(rotatedOrientation); if (suggestedCrop != null) { // only keep the visible part (without parallax) Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl); @@ -197,9 +188,9 @@ public class WallpaperCropper { // Case 4: if the device is a foldable, if we're looking for a folded orientation and have // the suggested crop of the relative unfolded orientation, reuse it by removing content. - int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation); + int unfoldedOrientation = defaultDisplayInfo.getUnfoldedOrientation(orientation); suggestedCrop = suggestedCrops.get(unfoldedOrientation); - suggestedDisplaySize = defaultDisplaySizes.get(unfoldedOrientation); + suggestedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(unfoldedOrientation); if (suggestedCrop != null) { // compute the visible part (without parallax) of the unfolded screen Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl); @@ -207,8 +198,11 @@ public class WallpaperCropper { Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE); // if we removed some width, add it back to add a parallax effect if (res.width() < adjustedCrop.width()) { - if (rtl) res.left = Math.min(res.left, adjustedCrop.left); - else res.right = Math.max(res.right, adjustedCrop.right); + if (rtl) { + res.left = Math.min(res.left, adjustedCrop.left); + } else { + res.right = Math.max(res.right, adjustedCrop.right); + } // use getAdjustedCrop(parallax=true) to make sure we don't exceed MAX_PARALLAX res = getAdjustedCrop(res, bitmapSize, displaySize, true, rtl, ADD); } @@ -218,9 +212,9 @@ public class WallpaperCropper { // Case 5: if the device is a foldable, if we're looking for an unfolded orientation and // have the suggested crop of the relative folded orientation, reuse it by adding content. - int foldedOrientation = mWallpaperDisplayHelper.getFoldedOrientation(orientation); + int foldedOrientation = defaultDisplayInfo.getFoldedOrientation(orientation); suggestedCrop = suggestedCrops.get(foldedOrientation); - suggestedDisplaySize = defaultDisplaySizes.get(foldedOrientation); + suggestedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(foldedOrientation); if (suggestedCrop != null) { // only keep the visible part (without parallax) Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl); @@ -229,17 +223,19 @@ public class WallpaperCropper { // Case 6: for a foldable device, try to combine case 3 + case 4 or 5: // rotate, then fold or unfold - Point rotatedDisplaySize = defaultDisplaySizes.get(rotatedOrientation); + Point rotatedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(rotatedOrientation); if (rotatedDisplaySize != null) { - int rotatedFolded = mWallpaperDisplayHelper.getFoldedOrientation(rotatedOrientation); - int rotateUnfolded = mWallpaperDisplayHelper.getUnfoldedOrientation(rotatedOrientation); + int rotatedFolded = defaultDisplayInfo.getFoldedOrientation(rotatedOrientation); + int rotateUnfolded = defaultDisplayInfo.getUnfoldedOrientation(rotatedOrientation); for (int suggestedOrientation : new int[]{rotatedFolded, rotateUnfolded}) { suggestedCrop = suggestedCrops.get(suggestedOrientation); if (suggestedCrop != null) { - Rect rotatedCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl); + Rect rotatedCrop = getCrop(rotatedDisplaySize, defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl); SparseArray<Rect> rotatedCropMap = new SparseArray<>(); rotatedCropMap.put(rotatedOrientation, rotatedCrop); - return getCrop(displaySize, bitmapSize, rotatedCropMap, rtl); + return getCrop(displaySize, defaultDisplayInfo, bitmapSize, rotatedCropMap, + rtl); } } } @@ -248,8 +244,8 @@ public class WallpaperCropper { Slog.w(TAG, "Could not find a proper default crop for display: " + displaySize + ", bitmap size: " + bitmapSize + ", suggested crops: " + suggestedCrops + ", orientation: " + orientation + ", rtl: " + rtl - + ", defaultDisplaySizes: " + defaultDisplaySizes); - return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl); + + ", defaultDisplaySizes: " + defaultDisplayInfo.defaultDisplaySizes); + return getCrop(displaySize, defaultDisplayInfo, bitmapSize, new SparseArray<>(), rtl); } /** @@ -445,7 +441,7 @@ public class WallpaperCropper { Rect suggestedCrop = suggestedCrops.get(orientation); if (suggestedCrop != null) { adjustedSuggestedCrops.put(orientation, - getCrop(displaySize, bitmapSize, suggestedCrops, rtl)); + getCrop(displaySize, mDefaultDisplayInfo, bitmapSize, suggestedCrops, rtl)); } } @@ -455,7 +451,8 @@ public class WallpaperCropper { int orientation = defaultDisplaySizes.keyAt(i); if (result.contains(orientation)) continue; Point displaySize = defaultDisplaySizes.valueAt(i); - Rect newCrop = getCrop(displaySize, bitmapSize, adjustedSuggestedCrops, rtl); + Rect newCrop = getCrop(displaySize, mDefaultDisplayInfo, bitmapSize, + adjustedSuggestedCrops, rtl); result.put(orientation, newCrop); } return result; diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java index ba0262a8bd19..69f0ef7c430e 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java @@ -542,9 +542,11 @@ public class WallpaperDataParser { // to support back compatibility in B&R, save the crops for one orientation in the // legacy "cropLeft", "cropTop", "cropRight", "cropBottom" entries int orientationToPutInLegacyCrop = wallpaper.mOrientationWhenSet; - if (mWallpaperDisplayHelper.isFoldable()) { - int unfoldedOrientation = mWallpaperDisplayHelper - .getUnfoldedOrientation(orientationToPutInLegacyCrop); + WallpaperDefaultDisplayInfo defaultDisplayInfo = + mWallpaperDisplayHelper.getDefaultDisplayInfo(); + if (defaultDisplayInfo.isFoldable) { + int unfoldedOrientation = defaultDisplayInfo.getUnfoldedOrientation( + orientationToPutInLegacyCrop); if (unfoldedOrientation != ORIENTATION_UNKNOWN) { orientationToPutInLegacyCrop = unfoldedOrientation; } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDefaultDisplayInfo.java b/services/core/java/com/android/server/wallpaper/WallpaperDefaultDisplayInfo.java new file mode 100644 index 000000000000..dabe91968338 --- /dev/null +++ b/services/core/java/com/android/server/wallpaper/WallpaperDefaultDisplayInfo.java @@ -0,0 +1,207 @@ +/* + * 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.wallpaper; + +import static android.app.WallpaperManager.ORIENTATION_UNKNOWN; +import static android.app.WallpaperManager.getRotatedOrientation; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP; + +import android.app.WallpaperManager; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.SparseArray; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; + + +/** A data class for the default display attributes used in wallpaper related operations. */ +public final class WallpaperDefaultDisplayInfo { + /** + * A data class representing the screen orientations for a foldable device in the folded and + * unfolded states. + */ + @VisibleForTesting + static final class FoldableOrientations { + @WallpaperManager.ScreenOrientation + public final int foldedOrientation; + @WallpaperManager.ScreenOrientation + public final int unfoldedOrientation; + + FoldableOrientations(int foldedOrientation, int unfoldedOrientation) { + this.foldedOrientation = foldedOrientation; + this.unfoldedOrientation = unfoldedOrientation; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (!(other instanceof FoldableOrientations that)) return false; + return foldedOrientation == that.foldedOrientation + && unfoldedOrientation == that.unfoldedOrientation; + } + + @Override + public int hashCode() { + return Objects.hash(foldedOrientation, unfoldedOrientation); + } + } + + public final SparseArray<Point> defaultDisplaySizes; + public final boolean isLargeScreen; + public final boolean isFoldable; + @VisibleForTesting + final List<FoldableOrientations> foldableOrientations; + + public WallpaperDefaultDisplayInfo() { + this.defaultDisplaySizes = new SparseArray<>(); + this.isLargeScreen = false; + this.isFoldable = false; + this.foldableOrientations = Collections.emptyList(); + } + + public WallpaperDefaultDisplayInfo(WindowManager windowManager, Resources resources) { + Set<WindowMetrics> metrics = windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY); + boolean isFoldable = resources.getIntArray(R.array.config_foldedDeviceStates).length > 0; + if (isFoldable) { + this.foldableOrientations = getFoldableOrientations(metrics); + } else { + this.foldableOrientations = Collections.emptyList(); + } + this.defaultDisplaySizes = getDisplaySizes(metrics); + this.isLargeScreen = isLargeScreen(metrics); + this.isFoldable = isFoldable; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (!(other instanceof WallpaperDefaultDisplayInfo that)) return false; + return isLargeScreen == that.isLargeScreen && isFoldable == that.isFoldable + && defaultDisplaySizes.contentEquals(that.defaultDisplaySizes) + && Objects.equals(foldableOrientations, that.foldableOrientations); + } + + @Override + public int hashCode() { + return 31 * Objects.hash(isLargeScreen, isFoldable, foldableOrientations) + + defaultDisplaySizes.contentHashCode(); + } + + /** + * Returns the folded orientation corresponds to the {@code unfoldedOrientation} found in + * {@link #foldableOrientations}. If not found, returns + * {@link WallpaperManager.ORIENTATION_UNKNOWN}. + */ + public int getFoldedOrientation(int unfoldedOrientation) { + for (FoldableOrientations orientations : foldableOrientations) { + if (orientations.unfoldedOrientation == unfoldedOrientation) { + return orientations.foldedOrientation; + } + } + return ORIENTATION_UNKNOWN; + } + + /** + * Returns the unfolded orientation corresponds to the {@code foldedOrientation} found in + * {@link #foldableOrientations}. If not found, returns + * {@link WallpaperManager.ORIENTATION_UNKNOWN}. + */ + public int getUnfoldedOrientation(int foldedOrientation) { + for (FoldableOrientations orientations : foldableOrientations) { + if (orientations.foldedOrientation == foldedOrientation) { + return orientations.unfoldedOrientation; + } + } + return ORIENTATION_UNKNOWN; + } + + private static SparseArray<Point> getDisplaySizes(Set<WindowMetrics> displayMetrics) { + SparseArray<Point> displaySizes = new SparseArray<>(); + for (WindowMetrics metric : displayMetrics) { + Rect bounds = metric.getBounds(); + Point displaySize = new Point(bounds.width(), bounds.height()); + Point reversedDisplaySize = new Point(displaySize.y, displaySize.x); + for (Point point : List.of(displaySize, reversedDisplaySize)) { + int orientation = WallpaperManager.getOrientation(point); + // don't add an entry if there is already a larger display of the same orientation + Point display = displaySizes.get(orientation); + if (display == null || display.x * display.y < point.x * point.y) { + displaySizes.put(orientation, point); + } + } + } + return displaySizes; + } + + private static boolean isLargeScreen(Set<WindowMetrics> displayMetrics) { + float smallestWidth = Float.MAX_VALUE; + for (WindowMetrics metric : displayMetrics) { + Rect bounds = metric.getBounds(); + smallestWidth = Math.min(smallestWidth, bounds.width() / metric.getDensity()); + } + return smallestWidth >= LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP; + } + + /** + * Determines all potential foldable orientations, populating {@code + * outFoldableOrientationPairs} with pairs of (folded orientation, unfolded orientation). If + * {@code defaultDisplayMetrics} isn't for foldable, {@code outFoldableOrientationPairs} will + * not be populated. + */ + private static List<FoldableOrientations> getFoldableOrientations( + Set<WindowMetrics> defaultDisplayMetrics) { + if (defaultDisplayMetrics.size() != 2) { + return Collections.emptyList(); + } + List<FoldableOrientations> foldableOrientations = new ArrayList<>(); + float surface = 0; + int firstOrientation = -1; + for (WindowMetrics metric : defaultDisplayMetrics) { + Rect bounds = metric.getBounds(); + Point displaySize = new Point(bounds.width(), bounds.height()); + + int orientation = WallpaperManager.getOrientation(displaySize); + float newSurface = displaySize.x * displaySize.y + / (metric.getDensity() * metric.getDensity()); + if (surface <= 0) { + surface = newSurface; + firstOrientation = orientation; + } else { + FoldableOrientations orientations = (newSurface > surface) + ? new FoldableOrientations(firstOrientation, orientation) + : new FoldableOrientations(orientation, firstOrientation); + FoldableOrientations rotatedOrientations = new FoldableOrientations( + getRotatedOrientation(orientations.foldedOrientation), + getRotatedOrientation(orientations.unfoldedOrientation)); + foldableOrientations.add(orientations); + foldableOrientations.add(rotatedOrientations); + } + } + return Collections.unmodifiableList(foldableOrientations); + } +} diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java index 3636f5aa8f27..bff5fc9c49f3 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java @@ -16,31 +16,25 @@ package com.android.server.wallpaper; -import static android.app.WallpaperManager.ORIENTATION_UNKNOWN; -import static android.app.WallpaperManager.getRotatedOrientation; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.window.flags.Flags.multiCrop; import android.app.WallpaperManager; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Binder; import android.os.Debug; -import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; import android.view.WindowManager; -import android.view.WindowMetrics; import com.android.server.wm.WindowManagerInternal; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; import java.util.function.Consumer; /** @@ -59,65 +53,25 @@ class WallpaperDisplayHelper { } private static final String TAG = WallpaperDisplayHelper.class.getSimpleName(); - private static final float LARGE_SCREEN_MIN_DP = 600f; private final SparseArray<DisplayData> mDisplayDatas = new SparseArray<>(); private final DisplayManager mDisplayManager; private final WindowManagerInternal mWindowManagerInternal; - private final SparseArray<Point> mDefaultDisplaySizes = new SparseArray<>(); - // related orientations pairs for foldable (folded orientation, unfolded orientation) - private final List<Pair<Integer, Integer>> mFoldableOrientationPairs = new ArrayList<>(); - - private final boolean mIsFoldable; - private boolean mIsLargeScreen = false; + private final WallpaperDefaultDisplayInfo mDefaultDisplayInfo; WallpaperDisplayHelper( DisplayManager displayManager, WindowManager windowManager, WindowManagerInternal windowManagerInternal, - boolean isFoldable) { + Resources resources) { mDisplayManager = displayManager; mWindowManagerInternal = windowManagerInternal; - mIsFoldable = isFoldable; - if (!multiCrop()) return; - Set<WindowMetrics> metrics = windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY); - boolean populateOrientationPairs = isFoldable && metrics.size() == 2; - float surface = 0; - int firstOrientation = -1; - for (WindowMetrics metric: metrics) { - Rect bounds = metric.getBounds(); - Point displaySize = new Point(bounds.width(), bounds.height()); - Point reversedDisplaySize = new Point(displaySize.y, displaySize.x); - for (Point point : List.of(displaySize, reversedDisplaySize)) { - int orientation = WallpaperManager.getOrientation(point); - // don't add an entry if there is already a larger display of the same orientation - Point display = mDefaultDisplaySizes.get(orientation); - if (display == null || display.x * display.y < point.x * point.y) { - mDefaultDisplaySizes.put(orientation, point); - } - } - - mIsLargeScreen |= (displaySize.x / metric.getDensity() >= LARGE_SCREEN_MIN_DP); - - if (populateOrientationPairs) { - int orientation = WallpaperManager.getOrientation(displaySize); - float newSurface = displaySize.x * displaySize.y - / (metric.getDensity() * metric.getDensity()); - if (surface <= 0) { - surface = newSurface; - firstOrientation = orientation; - } else { - Pair<Integer, Integer> pair = (newSurface > surface) - ? new Pair<>(firstOrientation, orientation) - : new Pair<>(orientation, firstOrientation); - Pair<Integer, Integer> rotatedPair = new Pair<>( - getRotatedOrientation(pair.first), getRotatedOrientation(pair.second)); - mFoldableOrientationPairs.add(pair); - mFoldableOrientationPairs.add(rotatedPair); - } - } + if (!multiCrop()) { + mDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(); + return; } + mDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(windowManager, resources); } DisplayData getDisplayDataOrCreate(int displayId) { @@ -203,51 +157,21 @@ class WallpaperDisplayHelper { } SparseArray<Point> getDefaultDisplaySizes() { - return mDefaultDisplaySizes; + return mDefaultDisplayInfo.defaultDisplaySizes; } /** Return the number of pixel of the largest dimension of the default display */ int getDefaultDisplayLargestDimension() { + SparseArray<Point> defaultDisplaySizes = mDefaultDisplayInfo.defaultDisplaySizes; int result = -1; - for (int i = 0; i < mDefaultDisplaySizes.size(); i++) { - Point size = mDefaultDisplaySizes.valueAt(i); + for (int i = 0; i < defaultDisplaySizes.size(); i++) { + Point size = defaultDisplaySizes.valueAt(i); result = Math.max(result, Math.max(size.x, size.y)); } return result; } - boolean isFoldable() { - return mIsFoldable; - } - - /** - * Return true if any of the screens of the default display is considered large (DP >= 600) - */ - boolean isLargeScreen() { - return mIsLargeScreen; - } - - /** - * If a given orientation corresponds to an unfolded orientation on foldable, return the - * corresponding folded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the - * device is not a foldable. - */ - int getFoldedOrientation(int orientation) { - for (Pair<Integer, Integer> pair : mFoldableOrientationPairs) { - if (pair.second.equals(orientation)) return pair.first; - } - return ORIENTATION_UNKNOWN; - } - - /** - * If a given orientation corresponds to a folded orientation on foldable, return the - * corresponding unfolded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the - * device is not a foldable. - */ - int getUnfoldedOrientation(int orientation) { - for (Pair<Integer, Integer> pair : mFoldableOrientationPairs) { - if (pair.first.equals(orientation)) return pair.second; - } - return ORIENTATION_UNKNOWN; + public WallpaperDefaultDisplayInfo getDefaultDisplayInfo() { + return mDefaultDisplayInfo; } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index bac732637d8d..e7da33d50b27 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1666,12 +1666,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); displayManager.registerDisplayListener(mDisplayListener, null /* handler */); WindowManager windowManager = mContext.getSystemService(WindowManager.class); - boolean isFoldable = mContext.getResources() - .getIntArray(R.array.config_foldedDeviceStates).length > 0; mWallpaperDisplayHelper = new WallpaperDisplayHelper( - displayManager, windowManager, mWindowManagerInternal, isFoldable); + displayManager, windowManager, mWindowManagerInternal, mContext.getResources()); mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper); - mWindowManagerInternal.setWallpaperCropUtils(mWallpaperCropper::getCrop); mActivityManager = mContext.getSystemService(ActivityManager.class); if (mContext.getResources().getBoolean( @@ -2510,9 +2507,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub List<Rect> result = new ArrayList<>(); boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL; + WallpaperDefaultDisplayInfo defaultDisplayInfo = + mWallpaperDisplayHelper.getDefaultDisplayInfo(); for (Point displaySize : displaySizes) { - result.add(mWallpaperCropper.getCrop( - displaySize, croppedBitmapSize, adjustedRelativeSuggestedCrops, rtl)); + result.add(WallpaperCropper.getCrop(displaySize, defaultDisplayInfo, + croppedBitmapSize, adjustedRelativeSuggestedCrops, rtl)); } if (originalBitmap) result = WallpaperCropper.getOriginalCropHints(wallpaper, result); return result; @@ -2548,8 +2547,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub List<Rect> result = new ArrayList<>(); boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL; + WallpaperDefaultDisplayInfo defaultDisplayInfo = + mWallpaperDisplayHelper.getDefaultDisplayInfo(); for (Point displaySize : displaySizes) { - result.add(mWallpaperCropper.getCrop(displaySize, bitmapSize, defaultCrops, rtl)); + result.add(WallpaperCropper.getCrop(displaySize, defaultDisplayInfo, bitmapSize, + defaultCrops, rtl)); } return result; } diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index a94183849bc5..e2b47b92f232 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -112,7 +112,6 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.apphibernation.AppHibernationManagerInternal; import com.android.server.apphibernation.AppHibernationService; -import com.android.window.flags.Flags; import java.util.ArrayList; import java.util.concurrent.TimeUnit; @@ -807,14 +806,8 @@ class ActivityMetricsLogger { } final Task otherTask = otherInfo.mLastLaunchedActivity.getTask(); // The adjacent task is the split root in which activities are started - final boolean isDescendantOfAdjacent; - if (Flags.allowMultipleAdjacentTaskFragments()) { - isDescendantOfAdjacent = launchedSplitRootTask.forOtherAdjacentTasks( - otherTask::isDescendantOf); - } else { - isDescendantOfAdjacent = otherTask.isDescendantOf( - launchedSplitRootTask.getAdjacentTask()); - } + final boolean isDescendantOfAdjacent = launchedSplitRootTask.forOtherAdjacentTasks( + otherTask::isDescendantOf); if (isDescendantOfAdjacent) { if (DEBUG_METRICS) { Slog.i(TAG, "Found adjacent tasks t1=" + launchedActivityTask.mTaskId diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 3cd4db7d8dfc..e91d88901751 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1716,6 +1716,7 @@ final class ActivityRecord extends WindowToken { } mAppCompatController.getLetterboxPolicy().onMovedToDisplay(mDisplayContent.getDisplayId()); + mAppCompatController.getDisplayCompatModePolicy().onMovedToDisplay(); } void layoutLetterboxIfNeeded(WindowState winHint) { @@ -3801,19 +3802,10 @@ final class ActivityRecord extends WindowToken { final TaskFragment taskFragment = getTaskFragment(); if (next != null && taskFragment != null && taskFragment.isEmbedded()) { final TaskFragment organized = taskFragment.getOrganizedTaskFragment(); - if (Flags.allowMultipleAdjacentTaskFragments()) { - delayRemoval = organized != null - && organized.topRunningActivity() == null - && organized.isDelayLastActivityRemoval() - && organized.forOtherAdjacentTaskFragments(next::isDescendantOf); - } else { - final TaskFragment adjacent = - organized != null ? organized.getAdjacentTaskFragment() : null; - if (adjacent != null && next.isDescendantOf(adjacent) - && organized.topRunningActivity() == null) { - delayRemoval = organized.isDelayLastActivityRemoval(); - } - } + delayRemoval = organized != null + && organized.topRunningActivity() == null + && organized.isDelayLastActivityRemoval() + && organized.forOtherAdjacentTaskFragments(next::isDescendantOf); } // isNextNotYetVisible is to check if the next activity is invisible, or it has been @@ -4787,11 +4779,6 @@ final class ActivityRecord extends WindowToken { } // Make sure the embedded adjacent can also be shown. - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final ActivityRecord adjacentActivity = taskFragment.getAdjacentTaskFragment() - .getTopNonFinishingActivity(); - return canShowWhenLocked(adjacentActivity); - } final boolean hasAdjacentNotAllowToShow = taskFragment.forOtherAdjacentTaskFragments( adjacentTF -> !canShowWhenLocked(adjacentTF.getTopNonFinishingActivity())); return !hasAdjacentNotAllowToShow; @@ -8980,6 +8967,7 @@ final class ActivityRecord extends WindowToken { // Reset the existing override configuration so it can be updated according to the latest // configuration. mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode(); + mAppCompatController.getDisplayCompatModePolicy().onProcessRestarted(); if (!attachedToProcess()) { return; diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java index cb122f2080a2..0f1939bfbb49 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java @@ -34,7 +34,6 @@ import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider; -import com.android.window.flags.Flags; import java.io.File; import java.io.PrintWriter; @@ -532,26 +531,6 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord final int currentIndex = currTF.asTask() != null ? currentTask.mChildren.indexOf(currentActivity) : currentTask.mChildren.indexOf(currTF); - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final int prevAdjacentIndex = currentTask.mChildren.indexOf( - prevTF.getAdjacentTaskFragment()); - if (prevAdjacentIndex > currentIndex) { - // PrevAdjacentTF already above currentActivity - return; - } - // Add both the one below, and its adjacent. - if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) { - result.add(initPrev); - } - final ActivityRecord prevAdjacentActivity = prevTF.getAdjacentTaskFragment() - .getTopMostActivity(); - if (prevAdjacentActivity != null && (!inTransition - || isInParticipant(prevAdjacentActivity, mTmpTransitionParticipants))) { - result.add(prevAdjacentActivity); - } - return; - } - final boolean hasAdjacentAboveCurrent = prevTF.forOtherAdjacentTaskFragments( prevAdjacentTF -> { final int prevAdjacentIndex = currentTask.mChildren.indexOf(prevAdjacentTF); diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 3f24da9d89f2..51025d204b46 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -60,6 +60,7 @@ import com.android.server.wm.ActivityStarter.DefaultFactory; import com.android.server.wm.ActivityStarter.Factory; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.List; /** @@ -97,6 +98,9 @@ public class ActivityStartController { /** Whether an {@link ActivityStarter} is currently executing (starting an Activity). */ private boolean mInExecution = false; + /** The {@link TaskDisplayArea}s that are currently starting home activity. */ + private ArrayList<TaskDisplayArea> mHomeLaunchingTaskDisplayAreas = new ArrayList<>(); + /** * TODO(b/64750076): Capture information necessary for dump and * {@link #postStartActivityProcessingForLastStarter} rather than keeping the entire object @@ -162,6 +166,11 @@ public class ActivityStartController { void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason, TaskDisplayArea taskDisplayArea) { + if (mHomeLaunchingTaskDisplayAreas.contains(taskDisplayArea)) { + Slog.e(TAG, "Abort starting home on " + taskDisplayArea + " recursively."); + return; + } + final ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); if (!ActivityRecord.isResolverActivity(aInfo.name)) { @@ -186,13 +195,18 @@ public class ActivityStartController { mSupervisor.endDeferResume(); } - mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason) - .setOutActivity(tmpOutRecord) - .setCallingUid(0) - .setActivityInfo(aInfo) - .setActivityOptions(options.toBundle(), - Binder.getCallingPid(), Binder.getCallingUid()) - .execute(); + try { + mHomeLaunchingTaskDisplayAreas.add(taskDisplayArea); + mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason) + .setOutActivity(tmpOutRecord) + .setCallingUid(0) + .setActivityInfo(aInfo) + .setActivityOptions(options.toBundle(), + Binder.getCallingPid(), Binder.getCallingUid()) + .execute(); + } finally { + mHomeLaunchingTaskDisplayAreas.remove(taskDisplayArea); + } mLastHomeActivityStartRecord = tmpOutRecord[0]; if (rootHomeTask.mInResumeTopActivity) { // If we are in resume section already, home activity will be initialized, but not @@ -479,9 +493,9 @@ public class ActivityStartController { } } catch (SecurityException securityException) { ActivityStarter.logAndThrowExceptionForIntentRedirect(mService.mContext, - "Creator URI Grant Caused Exception.", intent, creatorUid, - creatorPackage, filterCallingUid, callingPackage, - securityException); + ActivityStarter.INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION, + intent, creatorUid, creatorPackage, filterCallingUid, + callingPackage, securityException); } } if ((aInfo.applicationInfo.privateFlags @@ -720,6 +734,12 @@ public class ActivityStartController { } } + if (!mHomeLaunchingTaskDisplayAreas.isEmpty()) { + dumped = true; + pw.print(prefix); + pw.println("mHomeLaunchingTaskDisplayAreas:" + mHomeLaunchingTaskDisplayAreas); + } + if (!dumped) { pw.print(prefix); pw.println("(nothing)"); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 233f91385ca4..a84a008f66eb 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -65,6 +65,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TAS import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONFIGURATION; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS; +import static com.android.internal.util.FrameworkStatsLog.INTENT_REDIRECT_BLOCKED; import static com.android.server.pm.PackageArchiver.isArchivingEnabled; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW; @@ -140,6 +141,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.HeavyWeightSwitcherActivity; import com.android.internal.app.IVoiceInteractor; import com.android.internal.protolog.ProtoLog; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.UiThread; import com.android.server.am.ActivityManagerService.IntentCreatorToken; import com.android.server.am.PendingIntentRecord; @@ -623,7 +625,7 @@ class ActivityStarter { if ((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN) != 0) { logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext, - "Unparceled intent does not have a creator token set.", intent, + ActivityStarter.INTENT_REDIRECT_EXCEPTION_MISSING_OR_INVALID_TOKEN, intent, intentCreatorUid, intentCreatorPackage, resolvedCallingUid, resolvedCallingPackage, null); } @@ -659,9 +661,9 @@ class ActivityStarter { } } catch (SecurityException securityException) { logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext, - "Creator URI Grant Caused Exception.", intent, intentCreatorUid, - intentCreatorPackage, resolvedCallingUid, - resolvedCallingPackage, securityException); + ActivityStarter.INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION, + intent, intentCreatorUid, intentCreatorPackage, + resolvedCallingUid, resolvedCallingPackage, securityException); } } } else { @@ -683,9 +685,9 @@ class ActivityStarter { } } catch (SecurityException securityException) { logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext, - "Creator URI Grant Caused Exception.", intent, intentCreatorUid, - intentCreatorPackage, resolvedCallingUid, - resolvedCallingPackage, securityException); + ActivityStarter.INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION, + intent, intentCreatorUid, intentCreatorPackage, + resolvedCallingUid, resolvedCallingPackage, securityException); } } } @@ -1109,8 +1111,11 @@ class ActivityStarter { if (sourceRecord != null) { if (requestCode >= 0 && !sourceRecord.finishing) { resultRecord = sourceRecord; + request.logMessage.append(" (rr="); + } else { + request.logMessage.append(" (sr="); } - request.logMessage.append(" (sr=" + System.identityHashCode(sourceRecord) + ")"); + request.logMessage.append(System.identityHashCode(sourceRecord) + ")"); } } @@ -1261,27 +1266,27 @@ class ActivityStarter { request.ignoreTargetSecurity, inTask != null, null, resultRecord, resultRootTask)) { abort = logAndAbortForIntentRedirect(mService.mContext, - "Creator checkStartAnyActivityPermission Caused abortion.", + ActivityStarter.INTENT_REDIRECT_ABORT_START_ANY_ACTIVITY_PERMISSION, intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage); } } catch (SecurityException e) { logAndThrowExceptionForIntentRedirect(mService.mContext, - "Creator checkStartAnyActivityPermission Caused Exception.", + ActivityStarter.INTENT_REDIRECT_EXCEPTION_START_ANY_ACTIVITY_PERMISSION, intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage, e); } if (!mService.mIntentFirewall.checkStartActivity(intent, intentCreatorUid, 0, resolvedType, aInfo.applicationInfo)) { abort = logAndAbortForIntentRedirect(mService.mContext, - "Creator IntentFirewall.checkStartActivity Caused abortion.", + ActivityStarter.INTENT_REDIRECT_ABORT_INTENT_FIREWALL_START_ACTIVITY, intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage); } if (!mService.getPermissionPolicyInternal().checkStartActivity(intent, intentCreatorUid, intentCreatorPackage)) { abort = logAndAbortForIntentRedirect(mService.mContext, - "Creator PermissionPolicyService.checkStartActivity Caused abortion.", + ActivityStarter.INTENT_REDIRECT_ABORT_PERMISSION_POLICY_START_ACTIVITY, intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage); } } @@ -3626,13 +3631,41 @@ class ActivityStarter { pw.println(mInTaskFragment); } + /** + * Error codes for intent redirect. + * + * @hide + */ + @IntDef(prefix = {"INTENT_REDIRECT_"}, value = { + INTENT_REDIRECT_EXCEPTION_MISSING_OR_INVALID_TOKEN, + INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION, + INTENT_REDIRECT_EXCEPTION_START_ANY_ACTIVITY_PERMISSION, + INTENT_REDIRECT_ABORT_START_ANY_ACTIVITY_PERMISSION, + INTENT_REDIRECT_ABORT_INTENT_FIREWALL_START_ACTIVITY, + INTENT_REDIRECT_ABORT_PERMISSION_POLICY_START_ACTIVITY, + }) + @Retention(RetentionPolicy.SOURCE) + @interface IntentRedirectErrorCode { + } + + /** + * Error codes for intent redirect issues + */ + static final int INTENT_REDIRECT_EXCEPTION_MISSING_OR_INVALID_TOKEN = 1; + static final int INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION = 2; + static final int INTENT_REDIRECT_EXCEPTION_START_ANY_ACTIVITY_PERMISSION = 3; + static final int INTENT_REDIRECT_ABORT_START_ANY_ACTIVITY_PERMISSION = 4; + static final int INTENT_REDIRECT_ABORT_INTENT_FIREWALL_START_ACTIVITY = 5; + static final int INTENT_REDIRECT_ABORT_PERMISSION_POLICY_START_ACTIVITY = 6; + static void logAndThrowExceptionForIntentRedirect(@NonNull Context context, - @NonNull String message, @NonNull Intent intent, int intentCreatorUid, + @IntentRedirectErrorCode int errorCode, @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage, int callingUid, @Nullable String callingPackage, @Nullable SecurityException originalException) { - String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid, + String msg = getIntentRedirectPreventedLogMessage(errorCode, intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage); Slog.wtf(TAG, msg); + FrameworkStatsLog.write(INTENT_REDIRECT_BLOCKED, intentCreatorUid, callingUid, errorCode); if (preventIntentRedirectShowToast()) { UiThread.getHandler().post( () -> Toast.makeText(context, @@ -3646,12 +3679,13 @@ class ActivityStarter { } private static boolean logAndAbortForIntentRedirect(@NonNull Context context, - @NonNull String message, @NonNull Intent intent, int intentCreatorUid, + @IntentRedirectErrorCode int errorCode, @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage, int callingUid, @Nullable String callingPackage) { - String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid, + String msg = getIntentRedirectPreventedLogMessage(errorCode, intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage); Slog.wtf(TAG, msg); + FrameworkStatsLog.write(INTENT_REDIRECT_BLOCKED, intentCreatorUid, callingUid, errorCode); if (preventIntentRedirectShowToast()) { UiThread.getHandler().post( () -> Toast.makeText(context, @@ -3662,11 +3696,38 @@ class ActivityStarter { ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION, callingUid); } - private static String getIntentRedirectPreventedLogMessage(@NonNull String message, + private static String getIntentRedirectPreventedLogMessage( + @IntentRedirectErrorCode int errorCode, @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage, int callingUid, @Nullable String callingPackage) { + String message = getIntentRedirectErrorMessageFromCode(errorCode); return "[IntentRedirect Hardening] " + message + " intentCreatorUid: " + intentCreatorUid + "; intentCreatorPackage: " + intentCreatorPackage + "; callingUid: " + callingUid + "; callingPackage: " + callingPackage + "; intent: " + intent; } + + private static String getIntentRedirectErrorMessageFromCode( + @IntentRedirectErrorCode int errorCode) { + return switch (errorCode) { + case INTENT_REDIRECT_EXCEPTION_MISSING_OR_INVALID_TOKEN -> + "INTENT_REDIRECT_EXCEPTION_MISSING_OR_INVALID_TOKEN" + + " (Unparceled intent does not have a creator token set, throw exception.)"; + case INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION -> + "INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION" + + " (Creator URI permission grant throw exception.)"; + case INTENT_REDIRECT_EXCEPTION_START_ANY_ACTIVITY_PERMISSION -> + "INTENT_REDIRECT_ABORT_START_ANY_ACTIVITY_PERMISSION" + + " (Creator checkStartAnyActivityPermission, throw exception)"; + case INTENT_REDIRECT_ABORT_START_ANY_ACTIVITY_PERMISSION -> + "INTENT_REDIRECT_ABORT_START_ANY_ACTIVITY_PERMISSION" + + " (Creator checkStartAnyActivityPermission, abort)"; + case INTENT_REDIRECT_ABORT_INTENT_FIREWALL_START_ACTIVITY -> + "INTENT_REDIRECT_ABORT_INTENT_FIREWALL_START_ACTIVITY" + + " (Creator IntentFirewall.checkStartActivity, abort)"; + case INTENT_REDIRECT_ABORT_PERMISSION_POLICY_START_ACTIVITY -> + "INTENT_REDIRECT_ABORT_PERMISSION_POLICY_START_ACTIVITY" + + " (Creator PermissionPolicyService.checkStartActivity, abort)"; + default -> "Unknown error code: " + errorCode; + }; + } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index a7f2153993bb..b0563128870a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -164,7 +164,6 @@ import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.pm.SaferIntentUtils; import com.android.server.utils.Slogf; import com.android.server.wm.ActivityMetricsLogger.LaunchingState; -import com.android.window.flags.Flags; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -2991,17 +2990,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { if (child.asTaskFragment() != null && child.asTaskFragment().hasAdjacentTaskFragment()) { - final boolean isAnyTranslucent; - if (Flags.allowMultipleAdjacentTaskFragments()) { - final TaskFragment.AdjacentSet set = - child.asTaskFragment().getAdjacentTaskFragments(); - isAnyTranslucent = set.forAllTaskFragments( - tf -> !isOpaque(tf), null); - } else { - final TaskFragment adjacent = child.asTaskFragment() - .getAdjacentTaskFragment(); - isAnyTranslucent = !isOpaque(child) || !isOpaque(adjacent); - } + final boolean isAnyTranslucent = !isOpaque(child) + || child.asTaskFragment().forOtherAdjacentTaskFragments( + tf -> !isOpaque(tf)); if (!isAnyTranslucent) { // This task fragment and all its adjacent task fragments are opaque, // consider it opaque even if it doesn't fill its parent. diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index 48f08e945a59..c479591a5e0d 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -46,6 +46,8 @@ class AppCompatController { private final AppCompatSizeCompatModePolicy mSizeCompatModePolicy; @NonNull private final AppCompatSandboxingPolicy mSandboxingPolicy; + @NonNull + private final AppCompatDisplayCompatModePolicy mDisplayCompatModePolicy; AppCompatController(@NonNull WindowManagerService wmService, @NonNull ActivityRecord activityRecord) { @@ -69,6 +71,7 @@ class AppCompatController { mSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(activityRecord, mAppCompatOverrides); mSandboxingPolicy = new AppCompatSandboxingPolicy(activityRecord); + mDisplayCompatModePolicy = new AppCompatDisplayCompatModePolicy(); } @NonNull @@ -151,6 +154,11 @@ class AppCompatController { return mSandboxingPolicy; } + @NonNull + AppCompatDisplayCompatModePolicy getDisplayCompatModePolicy() { + return mDisplayCompatModePolicy; + } + 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/AppCompatDisplayCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatDisplayCompatModePolicy.java new file mode 100644 index 000000000000..acf51707c894 --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatDisplayCompatModePolicy.java @@ -0,0 +1,39 @@ +/* + * 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 com.android.window.flags.Flags; + +/** + * Encapsulate app-compat logic for multi-display environments. + */ +class AppCompatDisplayCompatModePolicy { + + private boolean mIsRestartMenuEnabledForDisplayMove; + + boolean isRestartMenuEnabledForDisplayMove() { + return Flags.enableRestartMenuForConnectedDisplays() && mIsRestartMenuEnabledForDisplayMove; + } + + void onMovedToDisplay() { + mIsRestartMenuEnabledForDisplayMove = true; + } + + void onProcessRestarted() { + mIsRestartMenuEnabledForDisplayMove = false; + } +} diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java index b03aa5263927..0f1e36d70db2 100644 --- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java @@ -361,7 +361,10 @@ class AppCompatSizeCompatModePolicy { if (enableSizeCompatModeImprovementsForConnectedDisplays()) { overrideConfig.touchscreen = fullConfig.touchscreen; overrideConfig.navigation = fullConfig.navigation; - overrideConfig.fontScale = fullConfig.fontScale; + overrideConfig.keyboard = fullConfig.keyboard; + overrideConfig.keyboardHidden = fullConfig.keyboardHidden; + overrideConfig.hardKeyboardHidden = fullConfig.hardKeyboardHidden; + overrideConfig.navigationHidden = fullConfig.navigationHidden; } // The smallest screen width is the short side of screen bounds. Because the bounds // and density won't be changed, smallestScreenWidthDp is also fixed. diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index 146044008b3f..b91a12598e01 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -161,6 +161,9 @@ final class AppCompatUtils { top.mAppCompatController.getLetterboxOverrides() .isLetterboxEducationEnabled()); + appCompatTaskInfo.setRestartMenuEnabledForDisplayMove(top.mAppCompatController + .getDisplayCompatModePolicy().isRestartMenuEnabledForDisplayMove()); + final AppCompatAspectRatioOverrides aspectRatioOverrides = top.mAppCompatController.getAspectRatioOverrides(); appCompatTaskInfo.setUserFullscreenOverrideEnabled( diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index e9b7649e8cbd..dfe323c43abb 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -479,21 +479,16 @@ class BackNavigationController { } } else { // If adjacent TF has companion to current TF, those two TF will be closed together. - final TaskFragment adjacentTF; - if (Flags.allowMultipleAdjacentTaskFragments()) { - if (currTF.getAdjacentTaskFragments().size() > 2) { - throw new IllegalStateException( - "Not yet support 3+ adjacent for non-Task TFs"); - } - final TaskFragment[] tmpAdjacent = new TaskFragment[1]; - currTF.forOtherAdjacentTaskFragments(tf -> { - tmpAdjacent[0] = tf; - return true; - }); - adjacentTF = tmpAdjacent[0]; - } else { - adjacentTF = currTF.getAdjacentTaskFragment(); + if (currTF.getAdjacentTaskFragments().size() > 2) { + throw new IllegalStateException( + "Not yet support 3+ adjacent for non-Task TFs"); } + final TaskFragment[] tmpAdjacent = new TaskFragment[1]; + currTF.forOtherAdjacentTaskFragments(tf -> { + tmpAdjacent[0] = tf; + return true; + }); + final TaskFragment adjacentTF = tmpAdjacent[0]; if (isSecondCompanionToFirst(currTF, adjacentTF)) { // The two TFs are adjacent (visually displayed side-by-side), search if any // activity below the lowest one. @@ -553,15 +548,6 @@ class BackNavigationController { if (!prevTF.hasAdjacentTaskFragment()) { return; } - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment(); - final ActivityRecord prevActivityAdjacent = - prevTFAdjacent.getTopNonFinishingActivity(); - if (prevActivityAdjacent != null) { - outPrevActivities.add(prevActivityAdjacent); - } - return; - } prevTF.forOtherAdjacentTaskFragments(prevTFAdjacent -> { final ActivityRecord prevActivityAdjacent = prevTFAdjacent.getTopNonFinishingActivity(); @@ -577,14 +563,6 @@ class BackNavigationController { if (mainTF == null || !mainTF.hasAdjacentTaskFragment()) { return; } - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final TaskFragment adjacentTF = mainTF.getAdjacentTaskFragment(); - final ActivityRecord topActivity = adjacentTF.getTopNonFinishingActivity(); - if (topActivity != null) { - outList.add(topActivity); - } - return; - } mainTF.forOtherAdjacentTaskFragments(adjacentTF -> { final ActivityRecord topActivity = adjacentTF.getTopNonFinishingActivity(); if (topActivity != null) { diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 2287a687700c..f50a68cc5389 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -49,8 +49,8 @@ import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskS import static com.android.window.flags.Flags.balImprovedMetrics; import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator; import static com.android.window.flags.Flags.balShowToastsBlocked; -import static com.android.window.flags.Flags.balStrictModeRo; import static com.android.window.flags.Flags.balStrictModeGracePeriod; +import static com.android.window.flags.Flags.balStrictModeRo; import static java.lang.annotation.RetentionPolicy.SOURCE; import static java.util.Objects.requireNonNull; @@ -91,7 +91,6 @@ import com.android.internal.util.Preconditions; import com.android.server.UiThread; import com.android.server.am.PendingIntentRecord; import com.android.server.wm.BackgroundLaunchProcessController.BalCheckConfiguration; -import com.android.window.flags.Flags; import java.lang.annotation.Retention; import java.util.ArrayList; @@ -1687,14 +1686,6 @@ public class BackgroundActivityStartController { } // Check the adjacent fragment. - if (!Flags.allowMultipleAdjacentTaskFragments()) { - TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); - topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate); - if (topActivity == null) { - return bas; - } - return checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas); - } final BlockActivityStart[] out = { bas }; taskFragment.forOtherAdjacentTaskFragments(adjacentTaskFragment -> { final ActivityRecord top = adjacentTaskFragment.getActivity(topOfStackPredicate); diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java index f473b7b7e4fb..fcc697242ff6 100644 --- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java +++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java @@ -18,7 +18,7 @@ package com.android.server.wm; import static android.view.WindowManager.TRANSIT_CHANGE; -import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS; +import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN; import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY; import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS; import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields; @@ -140,8 +140,9 @@ class DeferredDisplayUpdater { if (displayInfoDiff == DIFF_EVERYTHING || !mDisplayContent.getLastHasContent() || !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) { - ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, - "DeferredDisplayUpdater: applying DisplayInfo immediately"); + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN, + "DeferredDisplayUpdater: applying DisplayInfo(%d x %d) immediately", + displayInfo.logicalWidth, displayInfo.logicalHeight); mLastWmDisplayInfo = displayInfo; applyLatestDisplayInfo(); @@ -151,17 +152,23 @@ class DeferredDisplayUpdater { // If there are non WM-specific display info changes, apply only these fields immediately if ((displayInfoDiff & DIFF_NOT_WM_DEFERRABLE) > 0) { - ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, - "DeferredDisplayUpdater: partially applying DisplayInfo immediately"); + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN, + "DeferredDisplayUpdater: partially applying DisplayInfo(%d x %d) immediately", + displayInfo.logicalWidth, displayInfo.logicalHeight); applyLatestDisplayInfo(); } // If there are WM-specific display info changes, apply them through a Shell transition if ((displayInfoDiff & DIFF_WM_DEFERRABLE) > 0) { - ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, - "DeferredDisplayUpdater: deferring DisplayInfo update"); + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN, + "DeferredDisplayUpdater: deferring DisplayInfo(%d x %d) update", + displayInfo.logicalWidth, displayInfo.logicalHeight); requestDisplayChangeTransition(physicalDisplayUpdated, () -> { + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN, + "DeferredDisplayUpdater: applying DisplayInfo(%d x %d) after deferring", + displayInfo.logicalWidth, displayInfo.logicalHeight); + // Apply deferrable fields to DisplayContent only when the transition // starts collecting, non-deferrable fields are ignored in mLastWmDisplayInfo mLastWmDisplayInfo = displayInfo; @@ -199,7 +206,7 @@ class DeferredDisplayUpdater { mDisplayContent.getDisplayPolicy().getNotificationShade(); if (notificationShade != null && notificationShade.isVisible() && mDisplayContent.mAtmService.mKeyguardController.isKeyguardOrAodShowing( - mDisplayContent.mDisplayId)) { + mDisplayContent.mDisplayId)) { Slog.i(TAG, notificationShade + " uses blast for display switch"); notificationShade.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST; } @@ -209,9 +216,6 @@ class DeferredDisplayUpdater { try { onStartCollect.run(); - ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, - "DeferredDisplayUpdater: applied DisplayInfo after deferring"); - if (physicalDisplayUpdated) { onDisplayUpdated(transition, fromRotation, startBounds); } else { diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java index dc42b32967e2..d91fca9e2816 100644 --- a/services/core/java/com/android/server/wm/DesktopModeHelper.java +++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.app.Flags.enableConnectedDisplaysWallpaper; +import static android.window.DesktopExperienceFlags.ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE; import android.annotation.NonNull; import android.content.Context; @@ -66,7 +67,7 @@ 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) { + private static boolean canInternalDisplayHostDesktops(@NonNull Context context) { return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops); } @@ -83,8 +84,11 @@ public final class DesktopModeHelper { if (!shouldEnforceDeviceRestrictions()) { return true; } - final boolean desktopModeSupported = isDesktopModeSupported(context) - && canInternalDisplayHostDesktops(context); + // If projected display is enabled, #canInternalDisplayHostDesktops is no longer a + // requirement. + final boolean desktopModeSupported = ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE.isTrue() + ? isDesktopModeSupported(context) : (isDesktopModeSupported(context) + && canInternalDisplayHostDesktops(context)); final boolean desktopModeSupportedByDevOptions = Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionsSupported(context); diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java index d466a646c7dd..ddcb5eccb1d8 100644 --- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java @@ -19,6 +19,12 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; +import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK; +import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -131,6 +137,18 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { return RESULT_SKIP; } + if (DesktopModeFlags.INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES.isTrue()) { + ActivityRecord topVisibleFreeformActivity = + task.getDisplayContent().getTopMostVisibleFreeformActivity(); + if (shouldInheritExistingTaskBounds(topVisibleFreeformActivity, activity, task)) { + appendLog("inheriting bounds from existing closing instance"); + outParams.mBounds.set(topVisibleFreeformActivity.getBounds()); + appendLog("final desktop mode task bounds set to %s", outParams.mBounds); + // Return result done to prevent other modifiers from changing or cascading bounds. + return RESULT_DONE; + } + } + DesktopModeBoundsCalculator.updateInitialBounds(task, layout, activity, options, outParams.mBounds, this::appendLog); appendLog("final desktop mode task bounds set to %s", outParams.mBounds); @@ -159,7 +177,7 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { // activity will also enter desktop mode. On this same relationship, we can also assume // if there are not visible freeform tasks but a freeform activity is now launching, it // will force the device into desktop mode. - return (task.getDisplayContent().getTopMostVisibleFreeformActivity() != null + return (task.getDisplayContent().getTopMostFreeformActivity() != null && checkSourceWindowModesCompatible(task, options, currentParams)) || isRequestingFreeformWindowMode(task, options, currentParams); } @@ -201,6 +219,40 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { }; } + /** + * Whether the launching task should inherit the task bounds of an existing closing instance. + */ + private boolean shouldInheritExistingTaskBounds( + @Nullable ActivityRecord existingTaskActivity, + @Nullable ActivityRecord launchingActivity, + @NonNull Task launchingTask) { + if (existingTaskActivity == null || launchingActivity == null) return false; + return (existingTaskActivity.packageName == launchingActivity.packageName) + && isLaunchingNewTask(launchingActivity.launchMode, + launchingTask.getBaseIntent().getFlags()) + && isClosingExitingInstance(launchingTask.getBaseIntent().getFlags()); + } + + /** + * Returns true if the launch mode or intent will result in a new task being created for the + * activity. + */ + private boolean isLaunchingNewTask(int launchMode, int intentFlags) { + return launchMode == LAUNCH_SINGLE_TASK + || launchMode == LAUNCH_SINGLE_INSTANCE + || launchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK + || (intentFlags & FLAG_ACTIVITY_NEW_TASK) != 0; + } + + /** + * Returns true if the intent will result in an existing task instance being closed if a new + * one appears. + */ + private boolean isClosingExitingInstance(int intentFlags) { + return (intentFlags & FLAG_ACTIVITY_CLEAR_TASK) != 0 + || (intentFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0; + } + private void initLogBuilder(Task task, ActivityRecord activity) { if (DEBUG) { mLogBuilder = new StringBuilder( diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index a017a1173d97..e508a6d23178 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -23,8 +23,6 @@ import static com.android.server.wm.Task.TAG_VISIBILITY; import android.annotation.Nullable; import android.util.Slog; -import com.android.window.flags.Flags; - import java.util.ArrayList; /** Helper class to ensure activities are in the right visible state for a container. */ @@ -112,18 +110,11 @@ class EnsureActivitiesVisibleHelper { if (adjacentTaskFragments != null && adjacentTaskFragments.contains( childTaskFragment)) { - final boolean isTranslucent; - if (Flags.allowMultipleAdjacentTaskFragments()) { - isTranslucent = childTaskFragment.isTranslucent(starting) - || childTaskFragment.forOtherAdjacentTaskFragments( - adjacentTaskFragment -> { - return adjacentTaskFragment.isTranslucent(starting); - }); - } else { - isTranslucent = childTaskFragment.isTranslucent(starting) - || childTaskFragment.getAdjacentTaskFragment() - .isTranslucent(starting); - } + final boolean isTranslucent = childTaskFragment.isTranslucent(starting) + || childTaskFragment.forOtherAdjacentTaskFragments( + adjacentTaskFragment -> { + return adjacentTaskFragment.isTranslucent(starting); + }); if (!isTranslucent) { // Everything behind two adjacent TaskFragments are occluded. mBehindFullyOccludedContainer = true; @@ -135,14 +126,10 @@ class EnsureActivitiesVisibleHelper { if (adjacentTaskFragments == null) { adjacentTaskFragments = new ArrayList<>(); } - if (Flags.allowMultipleAdjacentTaskFragments()) { - final ArrayList<TaskFragment> adjacentTfs = adjacentTaskFragments; - childTaskFragment.forOtherAdjacentTaskFragments(adjacentTf -> { - adjacentTfs.add(adjacentTf); - }); - } else { - adjacentTaskFragments.add(childTaskFragment.getAdjacentTaskFragment()); - } + final ArrayList<TaskFragment> adjacentTfs = adjacentTaskFragments; + childTaskFragment.forOtherAdjacentTaskFragments(adjacentTf -> { + adjacentTfs.add(adjacentTf); + }); } } else if (child.asActivityRecord() != null) { setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity); diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 2cac63c1e5e9..a937691e7998 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -263,8 +263,8 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { boolean oldVisibility = mSource.isVisible(); super.updateVisibility(); if (Flags.refactorInsetsController()) { - if (mSource.isVisible() && !oldVisibility && mImeRequester != null) { - reportImeDrawnForOrganizerIfNeeded(mImeRequester); + if (mSource.isVisible() && !oldVisibility && mControlTarget != null) { + reportImeDrawnForOrganizerIfNeeded(mControlTarget); } } onSourceChanged(); diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 7751ac3f9fc6..a4bc5cbcb5d3 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -343,6 +343,13 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } } + @Override + public boolean isKeyguardLocked(int displayId) { + synchronized (mService.mGlobalLock) { + return mService.mAtmService.mKeyguardController.isKeyguardLocked(displayId); + } + } + /** Waits until the built-in input devices have been configured. */ public boolean waitForInputDevicesReady(long timeoutMillis) { synchronized (mInputDevicesReadyMonitor) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index cf201c9f34f0..609302ce3f56 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1732,26 +1732,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent> activityAssistInfos.clear(); activityAssistInfos.add(new ActivityAssistInfo(top)); // Check if the activity on the split screen. - if (Flags.allowMultipleAdjacentTaskFragments()) { - top.getTask().forOtherAdjacentTasks(task -> { - final ActivityRecord adjacentActivityRecord = - task.getTopNonFinishingActivity(); - if (adjacentActivityRecord != null) { - activityAssistInfos.add( - new ActivityAssistInfo(adjacentActivityRecord)); - } - }); - } else { - final Task adjacentTask = top.getTask().getAdjacentTask(); - if (adjacentTask != null) { - final ActivityRecord adjacentActivityRecord = - adjacentTask.getTopNonFinishingActivity(); - if (adjacentActivityRecord != null) { - activityAssistInfos.add( - new ActivityAssistInfo(adjacentActivityRecord)); - } + top.getTask().forOtherAdjacentTasks(task -> { + final ActivityRecord adjacentActivityRecord = + task.getTopNonFinishingActivity(); + if (adjacentActivityRecord != null) { + activityAssistInfos.add( + new ActivityAssistInfo(adjacentActivityRecord)); } - } + }); if (rootTask == topFocusedRootTask) { topVisibleActivities.addAll(0, activityAssistInfos); } else { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index d16c301cec40..e98b2b749af8 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2461,21 +2461,6 @@ class Task extends TaskFragment { return parentTask == null ? null : parentTask.getCreatedByOrganizerTask(); } - /** @deprecated b/373709676 replace with {@link #forOtherAdjacentTasks(Consumer)} ()}. */ - @Deprecated - @Nullable - Task getAdjacentTask() { - if (Flags.allowMultipleAdjacentTaskFragments()) { - throw new IllegalStateException("allowMultipleAdjacentTaskFragments is enabled. " - + "Use #forOtherAdjacentTasks instead"); - } - final Task taskWithAdjacent = getTaskWithAdjacent(); - if (taskWithAdjacent == null) { - return null; - } - return taskWithAdjacent.getAdjacentTaskFragment().asTask(); - } - /** Finds the first Task parent (or itself) that has adjacent. */ @Nullable Task getTaskWithAdjacent() { @@ -2499,11 +2484,6 @@ class Task extends TaskFragment { * Tasks. The invoke order is not guaranteed. */ void forOtherAdjacentTasks(@NonNull Consumer<Task> callback) { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - throw new IllegalStateException("allowMultipleAdjacentTaskFragments is not enabled. " - + "Use #getAdjacentTask instead"); - } - final Task taskWithAdjacent = getTaskWithAdjacent(); if (taskWithAdjacent == null) { return; @@ -2521,10 +2501,6 @@ class Task extends TaskFragment { * guaranteed. */ boolean forOtherAdjacentTasks(@NonNull Predicate<Task> callback) { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - throw new IllegalStateException("allowMultipleAdjacentTaskFragments is not enabled. " - + "Use getAdjacentTask instead"); - } final Task taskWithAdjacent = getTaskWithAdjacent(); if (taskWithAdjacent == null) { return false; @@ -3651,20 +3627,13 @@ class Task extends TaskFragment { final TaskFragment taskFragment = wc.asTaskFragment(); if (taskFragment != null && taskFragment.isEmbedded() && taskFragment.hasAdjacentTaskFragment()) { - if (Flags.allowMultipleAdjacentTaskFragments()) { - final int[] nextLayer = { layer }; - taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> { - if (adjacentTf.shouldBoostDimmer()) { - adjacentTf.assignLayer(t, nextLayer[0]++); - } - }); - layer = nextLayer[0]; - } else { - final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment(); + final int[] nextLayer = { layer }; + taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> { if (adjacentTf.shouldBoostDimmer()) { - adjacentTf.assignLayer(t, layer++); + adjacentTf.assignLayer(t, nextLayer[0]++); } - } + }); + layer = nextLayer[0]; } // Place the decor surface just above the owner TaskFragment. diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 1966ecf57c73..fb7bab4b3e26 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -60,7 +60,6 @@ import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.LaunchParamsController.LaunchParams; -import com.android.window.flags.Flags; import java.io.PrintWriter; import java.util.ArrayList; @@ -1089,19 +1088,14 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Use launch-adjacent-flag-root if launching with launch-adjacent flag. if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0 && mLaunchAdjacentFlagRootTask != null) { - final Task launchAdjacentRootAdjacentTask; - if (Flags.allowMultipleAdjacentTaskFragments()) { - final Task[] tmpTask = new Task[1]; - mLaunchAdjacentFlagRootTask.forOtherAdjacentTasks(task -> { - // TODO(b/382208145): enable FLAG_ACTIVITY_LAUNCH_ADJACENT for 3+. - // Find the first adjacent for now. - tmpTask[0] = task; - return true; - }); - launchAdjacentRootAdjacentTask = tmpTask[0]; - } else { - launchAdjacentRootAdjacentTask = mLaunchAdjacentFlagRootTask.getAdjacentTask(); - } + final Task[] tmpTask = new Task[1]; + mLaunchAdjacentFlagRootTask.forOtherAdjacentTasks(task -> { + // TODO(b/382208145): enable FLAG_ACTIVITY_LAUNCH_ADJACENT for 3+. + // Find the first adjacent for now. + tmpTask[0] = task; + return true; + }); + final Task launchAdjacentRootAdjacentTask = tmpTask[0]; if (sourceTask != null && (sourceTask == candidateTask || sourceTask.topRunningActivity() == null)) { // Do nothing when task that is getting opened is same as the source or when @@ -1129,14 +1123,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { if (launchRootTask == null || sourceTask == null) { return launchRootTask; } - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final Task adjacentRootTask = launchRootTask.getAdjacentTask(); - if (adjacentRootTask != null && (sourceTask == adjacentRootTask - || sourceTask.isDescendantOf(adjacentRootTask))) { - return adjacentRootTask; - } - return launchRootTask; - } final Task[] adjacentRootTask = new Task[1]; launchRootTask.forOtherAdjacentTasks(task -> { if (sourceTask == task || sourceTask.isDescendantOf(task)) { @@ -1163,24 +1149,16 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { return sourceTask.getCreatedByOrganizerTask(); } // Check if the candidate is already positioned in the adjacent Task. - if (Flags.allowMultipleAdjacentTaskFragments()) { - final Task[] adjacentRootTask = new Task[1]; - sourceTask.forOtherAdjacentTasks(task -> { - if (candidateTask == task || candidateTask.isDescendantOf(task)) { - adjacentRootTask[0] = task; - return true; - } - return false; - }); - if (adjacentRootTask[0] != null) { - return adjacentRootTask[0]; - } - } else { - final Task adjacentTarget = taskWithAdjacent.getAdjacentTask(); - if (candidateTask == adjacentTarget - || candidateTask.isDescendantOf(adjacentTarget)) { - return adjacentTarget; + final Task[] adjacentRootTask = new Task[1]; + sourceTask.forOtherAdjacentTasks(task -> { + if (candidateTask == task || candidateTask.isDescendantOf(task)) { + adjacentRootTask[0] = task; + return true; } + return false; + }); + if (adjacentRootTask[0] != null) { + return adjacentRootTask[0]; } return sourceTask.getCreatedByOrganizerTask(); } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 5183c6b57f15..2dabb253744a 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -235,11 +235,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** This task fragment will be removed when the cleanup of its children are done. */ private boolean mIsRemovalRequested; - /** @deprecated b/373709676 replace with {@link #mAdjacentTaskFragments} */ - @Deprecated - @Nullable - private TaskFragment mAdjacentTaskFragment; - /** * The TaskFragments that are adjacent to each other, including this TaskFragment. * All TaskFragments in this set share the same set instance. @@ -455,22 +450,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { return service.mWindowOrganizerController.getTaskFragment(token); } - /** @deprecated b/373709676 replace with {@link #setAdjacentTaskFragments}. */ - @Deprecated - void setAdjacentTaskFragment(@NonNull TaskFragment taskFragment) { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - if (mAdjacentTaskFragment == taskFragment) { - return; - } - resetAdjacentTaskFragment(); - mAdjacentTaskFragment = taskFragment; - taskFragment.setAdjacentTaskFragment(this); - return; - } - - setAdjacentTaskFragments(new AdjacentSet(this, taskFragment)); - } - void setAdjacentTaskFragments(@NonNull AdjacentSet adjacentTaskFragments) { adjacentTaskFragments.setAsAdjacent(); } @@ -483,56 +462,18 @@ class TaskFragment extends WindowContainer<WindowContainer> { return mCompanionTaskFragment; } - /** @deprecated b/373709676 replace with {@link #clearAdjacentTaskFragments()}. */ - @Deprecated - private void resetAdjacentTaskFragment() { - if (Flags.allowMultipleAdjacentTaskFragments()) { - throw new IllegalStateException("resetAdjacentTaskFragment shouldn't be called when" - + " allowMultipleAdjacentTaskFragments is enabled. Use either" - + " #clearAdjacentTaskFragments or #removeFromAdjacentTaskFragments"); - } - // Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment. - if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) { - mAdjacentTaskFragment.mAdjacentTaskFragment = null; - mAdjacentTaskFragment.mDelayLastActivityRemoval = false; - } - mAdjacentTaskFragment = null; - mDelayLastActivityRemoval = false; - } - void clearAdjacentTaskFragments() { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - resetAdjacentTaskFragment(); - return; - } - if (mAdjacentTaskFragments != null) { mAdjacentTaskFragments.clear(); } } void removeFromAdjacentTaskFragments() { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - resetAdjacentTaskFragment(); - return; - } - if (mAdjacentTaskFragments != null) { mAdjacentTaskFragments.remove(this); } } - /** @deprecated b/373709676 replace with {@link #getAdjacentTaskFragments()}. */ - @Deprecated - @Nullable - TaskFragment getAdjacentTaskFragment() { - if (Flags.allowMultipleAdjacentTaskFragments()) { - throw new IllegalStateException("allowMultipleAdjacentTaskFragments is enabled. " - + "Use #getAdjacentTaskFragments instead"); - } - return mAdjacentTaskFragment; - } - @Nullable AdjacentSet getAdjacentTaskFragments() { return mAdjacentTaskFragments; @@ -561,16 +502,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { } boolean hasAdjacentTaskFragment() { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - return mAdjacentTaskFragment != null; - } return mAdjacentTaskFragments != null; } boolean isAdjacentTo(@NonNull TaskFragment other) { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - return mAdjacentTaskFragment == other; - } return other != this && mAdjacentTaskFragments != null && mAdjacentTaskFragments.contains(other); @@ -1377,21 +1312,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (taskFragment.isAdjacentTo(this)) { continue; } - if (Flags.allowMultipleAdjacentTaskFragments()) { - final boolean isOccluding = mTmpRect.intersect(taskFragment.getBounds()) - || taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> { - return mTmpRect.intersect(adjacentTf.getBounds()); - }); - if (isOccluding) { - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } - } else { - final TaskFragment adjacentTaskFragment = - taskFragment.mAdjacentTaskFragment; - if (mTmpRect.intersect(taskFragment.getBounds()) - || mTmpRect.intersect(adjacentTaskFragment.getBounds())) { - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } + final boolean isOccluding = mTmpRect.intersect(taskFragment.getBounds()) + || taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> { + return mTmpRect.intersect(adjacentTf.getBounds()); + }); + if (isOccluding) { + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; } } } @@ -1427,37 +1353,22 @@ class TaskFragment extends WindowContainer<WindowContainer> { // 2. Adjacent TaskFragments do not overlap, so that if this TaskFragment is behind // any translucent TaskFragment in the adjacent set, then this TaskFragment is // visible behind translucent. - if (Flags.allowMultipleAdjacentTaskFragments()) { - final boolean hasTraversedAdj = otherTaskFrag.forOtherAdjacentTaskFragments( - adjacentTaskFragments::contains); - if (hasTraversedAdj) { - final boolean isTranslucent = - isBehindTransparentTaskFragment(otherTaskFrag, starting) - || otherTaskFrag.forOtherAdjacentTaskFragments( - (Predicate<TaskFragment>) tf -> - isBehindTransparentTaskFragment(tf, starting)); - if (isTranslucent) { - // Can be visible behind a translucent adjacent TaskFragments. - gotTranslucentFullscreen = true; - gotTranslucentAdjacent = true; - continue; - } - // Can not be visible behind adjacent TaskFragments. - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } - } else { - if (adjacentTaskFragments.contains(otherTaskFrag.mAdjacentTaskFragment)) { - if (isBehindTransparentTaskFragment(otherTaskFrag, starting) - || isBehindTransparentTaskFragment( - otherTaskFrag.mAdjacentTaskFragment, starting)) { - // Can be visible behind a translucent adjacent TaskFragments. - gotTranslucentFullscreen = true; - gotTranslucentAdjacent = true; - continue; - } - // Can not be visible behind adjacent TaskFragments. - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + final boolean hasTraversedAdj = otherTaskFrag.forOtherAdjacentTaskFragments( + adjacentTaskFragments::contains); + if (hasTraversedAdj) { + final boolean isTranslucent = + isBehindTransparentTaskFragment(otherTaskFrag, starting) + || otherTaskFrag.forOtherAdjacentTaskFragments( + (Predicate<TaskFragment>) tf -> + isBehindTransparentTaskFragment(tf, starting)); + if (isTranslucent) { + // Can be visible behind a translucent adjacent TaskFragments. + gotTranslucentFullscreen = true; + gotTranslucentAdjacent = true; + continue; } + // Can not be visible behind adjacent TaskFragments. + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; } adjacentTaskFragments.add(otherTaskFrag); } @@ -3299,40 +3210,23 @@ class TaskFragment extends WindowContainer<WindowContainer> { final ArrayList<WindowContainer> siblings = getParent().mChildren; final int zOrder = siblings.indexOf(this); - - if (!Flags.allowMultipleAdjacentTaskFragments()) { - if (siblings.indexOf(getAdjacentTaskFragment()) < zOrder) { - // early return if this TF already has higher z-ordering. - return false; - } - } else { - final boolean hasAdjacentOnTop = forOtherAdjacentTaskFragments( - tf -> siblings.indexOf(tf) > zOrder); - if (!hasAdjacentOnTop) { - // early return if this TF already has higher z-ordering. - return false; - } + final boolean hasAdjacentOnTop = forOtherAdjacentTaskFragments( + tf -> siblings.indexOf(tf) > zOrder); + if (!hasAdjacentOnTop) { + // early return if this TF already has higher z-ordering. + return false; } final ToBooleanFunction<WindowState> getDimBehindWindow = (w) -> (w.mAttrs.flags & FLAG_DIM_BEHIND) != 0 && w.mActivityRecord != null && w.mActivityRecord.isEmbedded() && (w.mActivityRecord.isVisibleRequested() || w.mActivityRecord.isVisible()); - - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final TaskFragment adjacentTf = getAdjacentTaskFragment(); - if (adjacentTf.forAllWindows(getDimBehindWindow, true)) { - // early return if the adjacent Tf has a dimming window. - return false; - } - } else { - final boolean adjacentHasDimmingWindow = forOtherAdjacentTaskFragments(tf -> { - return tf.forAllWindows(getDimBehindWindow, true); - }); - if (adjacentHasDimmingWindow) { - // early return if the adjacent Tf has a dimming window. - return false; - } + final boolean adjacentHasDimmingWindow = forOtherAdjacentTaskFragments(tf -> { + return tf.forAllWindows(getDimBehindWindow, true); + }); + if (adjacentHasDimmingWindow) { + // early return if the adjacent Tf has a dimming window. + return false; } // boost if there's an Activity window that has FLAG_DIM_BEHIND flag. @@ -3456,16 +3350,9 @@ class TaskFragment extends WindowContainer<WindowContainer> { sb.append(" organizerProc="); sb.append(mTaskFragmentOrganizerProcessName); } - if (Flags.allowMultipleAdjacentTaskFragments()) { - if (mAdjacentTaskFragments != null) { - sb.append(" adjacent="); - sb.append(mAdjacentTaskFragments); - } - } else { - if (mAdjacentTaskFragment != null) { - sb.append(" adjacent="); - sb.append(mAdjacentTaskFragment); - } + if (mAdjacentTaskFragments != null) { + sb.append(" adjacent="); + sb.append(mAdjacentTaskFragments); } sb.append('}'); return sb.toString(); @@ -3591,10 +3478,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { } AdjacentSet(@NonNull ArraySet<TaskFragment> taskFragments) { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - throw new IllegalStateException("allowMultipleAdjacentTaskFragments must be" - + " enabled to set more than two TaskFragments adjacent to each other."); - } final int size = taskFragments.size(); if (size < 2) { throw new IllegalArgumentException("Adjacent TaskFragments must contain at least" diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index c78cdaa10df2..803c21ccab6e 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2589,9 +2589,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } // When the TaskFragment has an adjacent TaskFragment, sibling behind them should be // hidden unless any of them are translucent. - if (!Flags.allowMultipleAdjacentTaskFragments()) { - return taskFragment.getAdjacentTaskFragment().isTranslucentForTransition(); - } return taskFragment.forOtherAdjacentTaskFragments(TaskFragment::isTranslucentForTransition); } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 3a4d9d27f65a..e1553cd37d03 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -57,7 +57,8 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ToBooleanFunction; -import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils; +import com.android.server.wallpaper.WallpaperCropper; +import com.android.server.wallpaper.WallpaperDefaultDisplayInfo; import java.io.PrintWriter; import java.util.ArrayList; @@ -71,7 +72,6 @@ import java.util.function.Consumer; class WallpaperController { private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperController" : TAG_WM; private WindowManagerService mService; - private WallpaperCropUtils mWallpaperCropUtils = null; private DisplayContent mDisplayContent; // Larger index has higher z-order. @@ -116,6 +116,10 @@ class WallpaperController { private boolean mShouldOffsetWallpaperCenter; + // This is for WallpaperCropper, which has cropping logic for the default display only. + // TODO(b/400685784) make the WallpaperCropper operate on every display independently + private final WallpaperDefaultDisplayInfo mDefaultDisplayInfo; + private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> { final ActivityRecord ar = w.mActivityRecord; // The animating window can still be visible on screen if it is in transition, so we @@ -198,12 +202,14 @@ class WallpaperController { WallpaperController(WindowManagerService service, DisplayContent displayContent) { mService = service; mDisplayContent = displayContent; + WindowManager windowManager = service.mContext.getSystemService(WindowManager.class); Resources resources = service.mContext.getResources(); mMinWallpaperScale = resources.getFloat(com.android.internal.R.dimen.config_wallpaperMinScale); mMaxWallpaperScale = resources.getFloat(R.dimen.config_wallpaperMaxScale); mShouldOffsetWallpaperCenter = resources.getBoolean( com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay); + mDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(windowManager, resources); } void resetLargestDisplay(Display display) { @@ -246,10 +252,6 @@ class WallpaperController { return largestDisplaySize; } - void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) { - mWallpaperCropUtils = wallpaperCropUtils; - } - WindowState getWallpaperTarget() { return mWallpaperTarget; } @@ -352,16 +354,12 @@ class WallpaperController { int offsetY; if (multiCrop()) { - if (mWallpaperCropUtils == null) { - Slog.e(TAG, "Update wallpaper offsets before the system is ready. Aborting"); - return false; - } Point bitmapSize = new Point( wallpaperWin.mRequestedWidth, wallpaperWin.mRequestedHeight); SparseArray<Rect> cropHints = token.getCropHints(); wallpaperFrame = bitmapSize.x <= 0 || bitmapSize.y <= 0 ? wallpaperWin.getFrame() - : mWallpaperCropUtils.getCrop(screenSize, bitmapSize, cropHints, - wallpaperWin.isRtl()); + : WallpaperCropper.getCrop(screenSize, mDefaultDisplayInfo, bitmapSize, + cropHints, wallpaperWin.isRtl()); int frameWidth = wallpaperFrame.width(); int frameHeight = wallpaperFrame.height(); float frameRatio = (float) frameWidth / frameHeight; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 466ed7863c84..772a7fdfc684 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2006,11 +2006,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return getActivity(r -> !r.finishing, true /* traverseTopToBottom */); } - ActivityRecord getTopMostVisibleFreeformActivity() { + ActivityRecord getTopMostFreeformActivity() { return getActivity(r -> r.isVisibleRequested() && r.inFreeformWindowingMode(), true /* traverseTopToBottom */); } + ActivityRecord getTopMostVisibleFreeformActivity() { + return getActivity(r -> r.isVisible() && r.inFreeformWindowingMode(), + true /* traverseTopToBottom */); + } + ActivityRecord getTopActivity(boolean includeFinishing, boolean includeOverlays) { // Break down into 4 calls to avoid object creation due to capturing input params. if (includeFinishing) { diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 4b5a3a031931..5f2a2ad7f0eb 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -54,7 +54,6 @@ import android.window.ScreenCapture.ScreenshotHardwareBuffer; import com.android.internal.policy.KeyInterceptionInfo; import com.android.server.input.InputManagerService; import com.android.server.policy.WindowManagerPolicy; -import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils; import com.android.server.wm.SensitiveContentPackages.PackageInfo; import java.lang.annotation.Retention; @@ -772,12 +771,6 @@ public abstract class WindowManagerInternal { public abstract void setWallpaperCropHints(IBinder windowToken, SparseArray<Rect> cropHints); /** - * Transmits the {@link WallpaperCropUtils} instance to {@link WallpaperController}. - * {@link WallpaperCropUtils} contains the helpers to properly position the wallpaper. - */ - public abstract void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils); - - /** * Returns {@code true} if a Window owned by {@code uid} has focus. */ public abstract boolean isUidFocused(int uid); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9fc0339c52a2..c078d67b6cc6 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -356,7 +356,6 @@ import com.android.server.policy.WindowManagerPolicy; import com.android.server.policy.WindowManagerPolicy.ScreenOffListener; import com.android.server.power.ShutdownThread; import com.android.server.utils.PriorityDump; -import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils; import com.android.window.flags.Flags; import dalvik.annotation.optimization.NeverCompile; @@ -8100,12 +8099,6 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) { - mRoot.getDisplayContent(DEFAULT_DISPLAY).mWallpaperController - .setWallpaperCropUtils(wallpaperCropUtils); - } - - @Override public boolean isUidFocused(int uid) { synchronized (mGlobalLock) { for (int i = mRoot.getChildCount() - 1; i >= 0; i--) { @@ -9374,23 +9367,6 @@ public class WindowManagerService extends IWindowManager.Stub return focusedActivity; } - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); - final ActivityRecord adjacentTopActivity = adjacentTaskFragment.topRunningActivity(); - if (adjacentTopActivity == null) { - // Return if no adjacent activity. - return focusedActivity; - } - - if (adjacentTopActivity.getLastWindowCreateTime() - < focusedActivity.getLastWindowCreateTime()) { - // Return if the current focus activity has more recently active window. - return focusedActivity; - } - - return adjacentTopActivity; - } - // Find the adjacent activity with more recently active window. final ActivityRecord[] mostRecentActiveActivity = { focusedActivity }; final long[] mostRecentActiveTime = { focusedActivity.getLastWindowCreateTime() }; @@ -9461,20 +9437,15 @@ public class WindowManagerService extends IWindowManager.Stub // No adjacent window. return false; } - final TaskFragment adjacentFragment; - if (Flags.allowMultipleAdjacentTaskFragments()) { - if (fromFragment.getAdjacentTaskFragments().size() > 2) { - throw new IllegalStateException("Not yet support 3+ adjacent for non-Task TFs"); - } - final TaskFragment[] tmpAdjacent = new TaskFragment[1]; - fromFragment.forOtherAdjacentTaskFragments(adjacentTF -> { - tmpAdjacent[0] = adjacentTF; - return true; - }); - adjacentFragment = tmpAdjacent[0]; - } else { - adjacentFragment = fromFragment.getAdjacentTaskFragment(); + if (fromFragment.getAdjacentTaskFragments().size() > 2) { + throw new IllegalStateException("Not yet support 3+ adjacent for non-Task TFs"); } + final TaskFragment[] tmpAdjacent = new TaskFragment[1]; + fromFragment.forOtherAdjacentTaskFragments(adjacentTF -> { + tmpAdjacent[0] = adjacentTF; + return true; + }); + final TaskFragment adjacentFragment = tmpAdjacent[0]; if (adjacentFragment.isIsolatedNav()) { // Don't move the focus if the adjacent TF is isolated navigation. return false; diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index ea1f35a130b0..a012ec137892 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1671,13 +1671,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } if (!taskFragment.isAdjacentTo(secondaryTaskFragment)) { // Only have lifecycle effect if the adjacent changed. - if (Flags.allowMultipleAdjacentTaskFragments()) { - // Activity Embedding only set two TFs adjacent. - taskFragment.setAdjacentTaskFragments( - new TaskFragment.AdjacentSet(taskFragment, secondaryTaskFragment)); - } else { - taskFragment.setAdjacentTaskFragment(secondaryTaskFragment); - } + // Activity Embedding only set two TFs adjacent. + taskFragment.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(taskFragment, secondaryTaskFragment)); effects |= TRANSACT_EFFECTS_LIFECYCLE; } @@ -2220,30 +2216,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } private int setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final WindowContainer wc1 = WindowContainer.fromBinder(hop.getContainer()); - if (wc1 == null || !wc1.isAttached()) { - Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc1); - return TRANSACT_EFFECTS_NONE; - } - final TaskFragment root1 = wc1.asTaskFragment(); - final WindowContainer wc2 = WindowContainer.fromBinder(hop.getAdjacentRoot()); - if (wc2 == null || !wc2.isAttached()) { - Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc2); - return TRANSACT_EFFECTS_NONE; - } - final TaskFragment root2 = wc2.asTaskFragment(); - if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) { - throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by" - + " organizer root1=" + root1 + " root2=" + root2); - } - if (root1.isAdjacentTo(root2)) { - return TRANSACT_EFFECTS_NONE; - } - root1.setAdjacentTaskFragment(root2); - return TRANSACT_EFFECTS_LIFECYCLE; - } - final IBinder[] containers = hop.getContainers(); final ArraySet<TaskFragment> adjacentRoots = new ArraySet<>(); for (IBinder container : containers) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 0ad976c38565..51ed6bb2aa40 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3806,9 +3806,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Update user switcher message to activity manager. ActivityManagerInternal activityManagerInternal = mInjector.getActivityManagerInternal(); - activityManagerInternal.setSwitchingFromSystemUserMessage( + int deviceOwnerUserId = UserHandle.getUserId(deviceOwner.getUid()); + activityManagerInternal.setSwitchingFromUserMessage(deviceOwnerUserId, deviceOwner.startUserSessionMessage); - activityManagerInternal.setSwitchingToSystemUserMessage( + activityManagerInternal.setSwitchingToUserMessage(deviceOwnerUserId, deviceOwner.endUserSessionMessage); } @@ -19716,7 +19717,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } mInjector.getActivityManagerInternal() - .setSwitchingFromSystemUserMessage(startUserSessionMessageString); + .setSwitchingFromUserMessage(caller.getUserId(), startUserSessionMessageString); } @Override @@ -19741,7 +19742,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } mInjector.getActivityManagerInternal() - .setSwitchingToSystemUserMessage(endUserSessionMessageString); + .setSwitchingToUserMessage(caller.getUserId(), endUserSessionMessageString); } @Override diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/OWNERS b/services/tests/DynamicInstrumentationManagerServiceTests/OWNERS new file mode 100644 index 000000000000..2522426d93f8 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/OWNERS @@ -0,0 +1 @@ +include platform/packages/modules/UprobeStats:/OWNERS
\ No newline at end of file diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp index ae9a34efc222..c1d8382fcd0e 100644 --- a/services/tests/InputMethodSystemServerTests/Android.bp +++ b/services/tests/InputMethodSystemServerTests/Android.bp @@ -73,10 +73,7 @@ android_ravenwood_test { static_libs: [ "androidx.annotation_annotation", "androidx.test.rules", - "framework", - "ravenwood-runtime", - "ravenwood-utils", - "services", + "services.core", ], libs: [ "android.test.base.stubs.system", diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java index 586ff52aa78c..7c239ef02e58 100644 --- a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java +++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java @@ -328,7 +328,6 @@ public class TestDreamEnvironment { case DREAM_STATE_STARTED -> startDream(); case DREAM_STATE_WOKEN -> wakeDream(); } - mTestableLooper.processAllMessages(); } while (mCurrentDreamState < state); return true; 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 5d8f57866f7d..e094111c327a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -746,6 +746,36 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") @Test @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY) + public void testUpdateOomAdjFreezeState_bindingWithAllowFreeze() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); + WindowProcessController wpc = app.getWindowProcessController(); + doReturn(true).when(wpc).hasVisibleActivities(); + + final ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, + MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + + // App with a visible activity binds to app2 without any special flag. + bindService(app2, app, null, null, 0, mock(IBinder.class)); + + final ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, + MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); + + // App with a visible activity binds to app3 with ALLOW_FREEZE. + bindService(app3, app, null, null, Context.BIND_ALLOW_FREEZE, mock(IBinder.class)); + + setProcessesToLru(app, app2, app3); + + updateOomAdj(app); + + assertCpuTime(app); + assertCpuTime(app2); + assertNoCpuTime(app3); + } + + @SuppressWarnings("GuardedBy") + @Test + @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY) @DisableFlags(Flags.FLAG_PROTOTYPE_AGGRESSIVE_FREEZING) public void testUpdateOomAdjFreezeState_bindingFromFgs() { final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java index 49c37f163ff2..241ffdc19ce4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java @@ -36,24 +36,29 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.internal.R; import org.junit.AfterClass; import org.junit.Before; @@ -70,10 +75,11 @@ import java.io.File; import java.io.IOException; import java.util.Comparator; import java.util.List; +import java.util.Set; /** * Unit tests for the most important helpers of {@link WallpaperCropper}, in particular - * {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}. + * {@link WallpaperCropper#getCrop(Point, WallpaperDefaultDisplayInfo, Point, SparseArray, boolean)}. */ @Presubmit @RunWith(AndroidJUnit4.class) @@ -83,6 +89,12 @@ public class WallpaperCropperTest { @Mock private WallpaperDisplayHelper mWallpaperDisplayHelper; + + @Mock + private WindowManager mWindowManager; + + @Mock + private Resources mResources; private WallpaperCropper mWallpaperCropper; private static final Point PORTRAIT_ONE = new Point(500, 800); @@ -175,14 +187,21 @@ public class WallpaperCropperTest { return tempDir; } - private void setUpWithDisplays(List<Point> displaySizes) { + private WallpaperDefaultDisplayInfo setUpWithDisplays(List<Point> displaySizes) { mDisplaySizes = new SparseArray<>(); displaySizes.forEach(size -> { mDisplaySizes.put(getOrientation(size), size); Point rotated = new Point(size.y, size.x); mDisplaySizes.put(getOrientation(rotated), rotated); }); + Set<WindowMetrics> windowMetrics = new ArraySet<>(); + for (Point displaySize : displaySizes) { + windowMetrics.add( + new WindowMetrics(new Rect(0, 0, displaySize.x, displaySize.y), + new WindowInsets.Builder().build())); + } when(mWallpaperDisplayHelper.getDefaultDisplaySizes()).thenReturn(mDisplaySizes); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())).thenReturn(windowMetrics); if (displaySizes.size() == 2) { Point largestDisplay = displaySizes.stream().max( Comparator.comparingInt(p -> p.x * p.y)).get(); @@ -192,11 +211,16 @@ public class WallpaperCropperTest { mFolded = getOrientation(smallestDisplay); mUnfoldedRotated = getRotatedOrientation(mUnfolded); mFoldedRotated = getRotatedOrientation(mFolded); + // foldable + doReturn(new int[]{0}).when(mResources).getIntArray(R.array.config_foldedDeviceStates); + } else { + // no foldable + doReturn(new int[]{}).when(mResources).getIntArray(R.array.config_foldedDeviceStates); } - doAnswer(invocation -> getFoldedOrientation(invocation.getArgument(0))) - .when(mWallpaperDisplayHelper).getFoldedOrientation(anyInt()); - doAnswer(invocation -> getUnfoldedOrientation(invocation.getArgument(0))) - .when(mWallpaperDisplayHelper).getUnfoldedOrientation(anyInt()); + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + when(mWallpaperDisplayHelper.getDefaultDisplayInfo()).thenReturn(defaultDisplayInfo); + return defaultDisplayInfo; } private int getFoldedOrientation(int orientation) { @@ -435,7 +459,7 @@ public class WallpaperCropperTest { */ @Test public void testGetCrop_noSuggestedCrops() { - setUpWithDisplays(STANDARD_DISPLAY); + WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(STANDARD_DISPLAY); Point bitmapSize = new Point(800, 1000); Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y); SparseArray<Rect> suggestedCrops = new SparseArray<>(); @@ -455,8 +479,9 @@ public class WallpaperCropperTest { for (boolean rtl : List.of(false, true)) { Rect expectedCrop = rtl ? rightOf(bitmapRect, expectedCropSize) : leftOf(bitmapRect, expectedCropSize); - assertThat(mWallpaperCropper.getCrop( - displaySize, bitmapSize, suggestedCrops, rtl)) + assertThat( + WallpaperCropper.getCrop( + displaySize, defaultDisplayInfo, bitmapSize, suggestedCrops, rtl)) .isEqualTo(expectedCrop); } } @@ -469,7 +494,7 @@ public class WallpaperCropperTest { */ @Test public void testGetCrop_hasSuggestedCrop() { - setUpWithDisplays(STANDARD_DISPLAY); + WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(STANDARD_DISPLAY); Point bitmapSize = new Point(800, 1000); SparseArray<Rect> suggestedCrops = new SparseArray<>(); suggestedCrops.put(ORIENTATION_PORTRAIT, new Rect(0, 0, 400, 800)); @@ -479,11 +504,13 @@ public class WallpaperCropperTest { } for (boolean rtl : List.of(false, true)) { - assertThat(mWallpaperCropper.getCrop( - new Point(300, 800), bitmapSize, suggestedCrops, rtl)) + assertThat( + WallpaperCropper.getCrop(new Point(300, 800), defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl)) .isEqualTo(suggestedCrops.get(ORIENTATION_PORTRAIT)); - assertThat(mWallpaperCropper.getCrop( - new Point(500, 800), bitmapSize, suggestedCrops, rtl)) + assertThat( + WallpaperCropper.getCrop(new Point(500, 800), defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl)) .isEqualTo(new Rect(0, 0, 500, 800)); } } @@ -499,7 +526,7 @@ public class WallpaperCropperTest { */ @Test public void testGetCrop_hasRotatedSuggestedCrop() { - setUpWithDisplays(STANDARD_DISPLAY); + WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(STANDARD_DISPLAY); Point bitmapSize = new Point(2000, 1800); Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y); SparseArray<Rect> suggestedCrops = new SparseArray<>(); @@ -510,12 +537,14 @@ public class WallpaperCropperTest { suggestedCrops.put(ORIENTATION_PORTRAIT, centerOf(bitmapRect, portrait)); suggestedCrops.put(ORIENTATION_SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape)); for (boolean rtl : List.of(false, true)) { - assertThat(mWallpaperCropper.getCrop( - landscape, bitmapSize, suggestedCrops, rtl)) + assertThat( + WallpaperCropper.getCrop(landscape, defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl)) .isEqualTo(centerOf(bitmapRect, landscape)); - assertThat(mWallpaperCropper.getCrop( - squarePortrait, bitmapSize, suggestedCrops, rtl)) + assertThat( + WallpaperCropper.getCrop(squarePortrait, defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl)) .isEqualTo(centerOf(bitmapRect, squarePortrait)); } } @@ -532,7 +561,7 @@ public class WallpaperCropperTest { @Test public void testGetCrop_hasUnfoldedSuggestedCrop() { for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) { - setUpWithDisplays(displaySizes); + WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes); Point bitmapSize = new Point(2000, 2400); Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y); @@ -569,8 +598,9 @@ public class WallpaperCropperTest { expectedCrop.right = Math.min( unfoldedCrop.right, unfoldedCrop.right + maxParallax); } - assertThat(mWallpaperCropper.getCrop( - foldedDisplay, bitmapSize, suggestedCrops, rtl)) + assertThat( + WallpaperCropper.getCrop(foldedDisplay, defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl)) .isEqualTo(expectedCrop); } } @@ -588,7 +618,7 @@ public class WallpaperCropperTest { @Test public void testGetCrop_hasFoldedSuggestedCrop() { for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) { - setUpWithDisplays(displaySizes); + WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes); Point bitmapSize = new Point(2000, 2000); Rect bitmapRect = new Rect(0, 0, 2000, 2000); @@ -610,12 +640,14 @@ public class WallpaperCropperTest { Point unfoldedDisplayTwo = mDisplaySizes.get(unfoldedTwo); for (boolean rtl : List.of(false, true)) { - assertThat(centerOf(mWallpaperCropper.getCrop( - unfoldedDisplayOne, bitmapSize, suggestedCrops, rtl), foldedDisplayOne)) + assertThat(centerOf( + WallpaperCropper.getCrop(unfoldedDisplayOne, defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl), foldedDisplayOne)) .isEqualTo(foldedCropOne); - assertThat(centerOf(mWallpaperCropper.getCrop( - unfoldedDisplayTwo, bitmapSize, suggestedCrops, rtl), foldedDisplayTwo)) + assertThat(centerOf( + WallpaperCropper.getCrop(unfoldedDisplayTwo, defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl), foldedDisplayTwo)) .isEqualTo(foldedCropTwo); } } @@ -633,7 +665,7 @@ public class WallpaperCropperTest { @Test public void testGetCrop_hasRotatedUnfoldedSuggestedCrop() { for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) { - setUpWithDisplays(displaySizes); + WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes); Point bitmapSize = new Point(2000, 2000); Rect bitmapRect = new Rect(0, 0, 2000, 2000); Point largestDisplay = displaySizes.stream().max( @@ -650,8 +682,9 @@ public class WallpaperCropperTest { Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded); for (boolean rtl : List.of(false, true)) { - assertThat(mWallpaperCropper.getCrop( - rotatedFoldedDisplay, bitmapSize, suggestedCrops, rtl)) + assertThat( + WallpaperCropper.getCrop(rotatedFoldedDisplay, defaultDisplayInfo, + bitmapSize, suggestedCrops, rtl)) .isEqualTo(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay)); } } @@ -670,7 +703,7 @@ public class WallpaperCropperTest { @Test public void testGetCrop_hasRotatedFoldedSuggestedCrop() { for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) { - setUpWithDisplays(displaySizes); + WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes); Point bitmapSize = new Point(2000, 2000); Rect bitmapRect = new Rect(0, 0, 2000, 2000); @@ -689,8 +722,8 @@ public class WallpaperCropperTest { Point rotatedUnfoldedDisplay = mDisplaySizes.get(rotatedUnfolded); for (boolean rtl : List.of(false, true)) { - Rect rotatedUnfoldedCrop = mWallpaperCropper.getCrop( - rotatedUnfoldedDisplay, bitmapSize, suggestedCrops, rtl); + Rect rotatedUnfoldedCrop = WallpaperCropper.getCrop(rotatedUnfoldedDisplay, + defaultDisplayInfo, bitmapSize, suggestedCrops, rtl); assertThat(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay)) .isEqualTo(rotatedFoldedCrop); } diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperDefaultDisplayInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperDefaultDisplayInfoTest.java new file mode 100644 index 000000000000..312db91afb12 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperDefaultDisplayInfoTest.java @@ -0,0 +1,340 @@ +/* + * 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.wallpaper; + +import static android.app.WallpaperManager.ORIENTATION_LANDSCAPE; +import static android.app.WallpaperManager.ORIENTATION_PORTRAIT; +import static android.app.WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE; +import static android.app.WallpaperManager.ORIENTATION_SQUARE_PORTRAIT; +import static android.app.WallpaperManager.ORIENTATION_UNKNOWN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.util.SparseArray; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.R; +import com.android.server.wallpaper.WallpaperDefaultDisplayInfo.FoldableOrientations; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.util.Set; + +/** Unit tests for {@link WallpaperDefaultDisplayInfo}. */ +@Presubmit +@RunWith(AndroidJUnit4.class) +public class WallpaperDefaultDisplayInfoTest { + @Mock + private WindowManager mWindowManager; + + @Mock + private Resources mResources; + + @Before + public void setUp() { + initMocks(this); + } + + @Test + public void defaultDisplayInfo_foldable_shouldHaveExpectedContent() { + doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152); + Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424); + WindowMetrics innerDisplayMetrics = + new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + WindowMetrics outerDisplayMetrics = + new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + SparseArray<Point> displaySizes = new SparseArray<>(); + displaySizes.put(ORIENTATION_PORTRAIT, new Point(1080, 2424)); + displaySizes.put(ORIENTATION_LANDSCAPE, new Point(2424, 1080)); + displaySizes.put(ORIENTATION_SQUARE_PORTRAIT, new Point(2076, 2152)); + displaySizes.put(ORIENTATION_SQUARE_LANDSCAPE, new Point(2152, 2076)); + assertThat(defaultDisplayInfo.defaultDisplaySizes.contentEquals(displaySizes)).isTrue(); + assertThat(defaultDisplayInfo.isFoldable).isTrue(); + assertThat(defaultDisplayInfo.isLargeScreen).isFalse(); + assertThat(defaultDisplayInfo.foldableOrientations).containsExactly( + new FoldableOrientations( + /* foldedOrientation= */ ORIENTATION_PORTRAIT, + /* unfoldedOrientation= */ ORIENTATION_SQUARE_PORTRAIT), + new FoldableOrientations( + /* foldedOrientation= */ ORIENTATION_LANDSCAPE, + /* unfoldedOrientation= */ ORIENTATION_SQUARE_LANDSCAPE)); + } + + @Test + public void defaultDisplayInfo_tablet_shouldHaveExpectedContent() { + doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect displayBounds = new Rect(0, 0, 2560, 1600); + WindowMetrics displayMetrics = + new WindowMetrics(displayBounds, new WindowInsets.Builder().build(), + /* density= */ 2f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(displayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + SparseArray<Point> displaySizes = new SparseArray<>(); + displaySizes.put(ORIENTATION_PORTRAIT, new Point(1600, 2560)); + displaySizes.put(ORIENTATION_LANDSCAPE, new Point(2560, 1600)); + assertThat(defaultDisplayInfo.defaultDisplaySizes.contentEquals(displaySizes)).isTrue(); + assertThat(defaultDisplayInfo.isFoldable).isFalse(); + assertThat(defaultDisplayInfo.isLargeScreen).isTrue(); + assertThat(defaultDisplayInfo.foldableOrientations).isEmpty(); + } + + @Test + public void defaultDisplayInfo_phone_shouldHaveExpectedContent() { + doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect displayBounds = new Rect(0, 0, 1280, 2856); + WindowMetrics displayMetrics = + new WindowMetrics(displayBounds, new WindowInsets.Builder().build(), + /* density= */ 3f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(displayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + SparseArray<Point> displaySizes = new SparseArray<>(); + displaySizes.put(ORIENTATION_PORTRAIT, new Point(1280, 2856)); + displaySizes.put(ORIENTATION_LANDSCAPE, new Point(2856, 1280)); + assertThat(defaultDisplayInfo.defaultDisplaySizes.contentEquals(displaySizes)).isTrue(); + assertThat(defaultDisplayInfo.isFoldable).isFalse(); + assertThat(defaultDisplayInfo.isLargeScreen).isFalse(); + assertThat(defaultDisplayInfo.foldableOrientations).isEmpty(); + } + + @Test + public void defaultDisplayInfo_equals_sameContent_shouldEqual() { + doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152); + Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424); + WindowMetrics innerDisplayMetrics = + new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + WindowMetrics outerDisplayMetrics = + new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo).isEqualTo(otherDefaultDisplayInfo); + } + + @Test + public void defaultDisplayInfo_equals_differentBounds_shouldNotEqual() { + doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152); + Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424); + WindowMetrics innerDisplayMetrics = + new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + WindowMetrics outerDisplayMetrics = + new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + // For the first call + .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics)) + // For the second+ call + .thenReturn(Set.of(innerDisplayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo).isNotEqualTo(otherDefaultDisplayInfo); + } + + @Test + public void defaultDisplayInfo_hashCode_sameContent_shouldEqual() { + doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152); + Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424); + WindowMetrics innerDisplayMetrics = + new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + WindowMetrics outerDisplayMetrics = + new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo.hashCode()).isEqualTo(otherDefaultDisplayInfo.hashCode()); + } + + @Test + public void defaultDisplayInfo_hashCode_differentBounds_shouldNotEqual() { + doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152); + Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424); + WindowMetrics innerDisplayMetrics = + new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + WindowMetrics outerDisplayMetrics = + new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + // For the first call + .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics)) + // For the second+ call + .thenReturn(Set.of(innerDisplayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo.hashCode()).isNotEqualTo(otherDefaultDisplayInfo.hashCode()); + } + + @Test + public void getFoldedOrientation_foldable_shouldReturnExpectedOrientation() { + doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152); + Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424); + WindowMetrics innerDisplayMetrics = + new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + WindowMetrics outerDisplayMetrics = + new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics)); + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_PORTRAIT)) + .isEqualTo(ORIENTATION_PORTRAIT); + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE)) + .isEqualTo(ORIENTATION_LANDSCAPE); + // Use a folded orientation for a folded orientation should return unknown. + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_PORTRAIT)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_LANDSCAPE)) + .isEqualTo(ORIENTATION_UNKNOWN); + } + + @Test + public void getUnfoldedOrientation_foldable_shouldReturnExpectedOrientation() { + doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152); + Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424); + WindowMetrics innerDisplayMetrics = + new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + WindowMetrics outerDisplayMetrics = + new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics)); + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_PORTRAIT)) + .isEqualTo(ORIENTATION_SQUARE_PORTRAIT); + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_LANDSCAPE)) + .isEqualTo(ORIENTATION_SQUARE_LANDSCAPE); + // Use an unfolded orientation for an unfolded orientation should return unknown. + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_PORTRAIT)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE)) + .isEqualTo(ORIENTATION_UNKNOWN); + } + + @Test + public void getFoldedOrientation_nonFoldable_shouldReturnUnknown() { + doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect displayBounds = new Rect(0, 0, 2560, 1600); + WindowMetrics displayMetrics = + new WindowMetrics(displayBounds, new WindowInsets.Builder().build(), + /* density= */ 2f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(displayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_PORTRAIT)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_PORTRAIT)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_LANDSCAPE)) + .isEqualTo(ORIENTATION_UNKNOWN); + } + + @Test + public void getUnFoldedOrientation_nonFoldable_shouldReturnUnknown() { + doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect displayBounds = new Rect(0, 0, 2560, 1600); + WindowMetrics displayMetrics = + new WindowMetrics(displayBounds, new WindowInsets.Builder().build(), + /* density= */ 2f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(displayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_PORTRAIT)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_PORTRAIT)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_LANDSCAPE)) + .isEqualTo(ORIENTATION_UNKNOWN); + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java index efea21428937..63c572af37b2 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java @@ -171,7 +171,7 @@ public class HearingDevicePhoneCallNotificationControllerTest { HearingDevicePhoneCallNotificationController.CallStateListener { TestCallStateListener(@NonNull Context context) { - super(context); + super(context, context.getMainExecutor()); } @Override 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 69877c372442..ea25e7992dd9 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 @@ -43,6 +43,7 @@ import android.view.accessibility.AccessibilityManager; import com.android.internal.accessibility.util.AccessibilityUtils; import com.android.server.accessibility.AccessibilityTraceManager; +import com.android.server.accessibility.BaseEventStreamTransformation; import org.junit.After; import org.junit.Before; @@ -70,6 +71,19 @@ public class AutoclickControllerTest { @Mock private WindowManager mMockWindowManager; private AutoclickController mController; + private static class MotionEventCaptor extends BaseEventStreamTransformation { + public MotionEvent downEvent; + + @Override + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + downEvent = event; + break; + } + } + } + @Before public void setUp() { mTestableLooper = TestableLooper.get(this); @@ -713,6 +727,100 @@ public class AutoclickControllerTest { assertThat(mController.mAutoclickScrollPanel.isVisible()).isFalse(); } + @Test + public void sendClick_clickType_leftClick() { + MotionEventCaptor motionEventCaptor = new MotionEventCaptor(); + mController.setNext(motionEventCaptor); + + injectFakeMouseActionHoverMoveEvent(); + // Set delay to zero so click is scheduled to run immediately. + mController.mClickScheduler.updateDelay(0); + + // 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); + mTestableLooper.processAllMessages(); + + // Verify left click sent. + assertThat(motionEventCaptor.downEvent).isNotNull(); + assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo( + MotionEvent.BUTTON_PRIMARY); + } + + @Test + public void sendClick_clickType_rightClick() { + MotionEventCaptor motionEventCaptor = new MotionEventCaptor(); + mController.setNext(motionEventCaptor); + + injectFakeMouseActionHoverMoveEvent(); + // Set delay to zero so click is scheduled to run immediately. + mController.mClickScheduler.updateDelay(0); + + // Set click type to right click. + mController.clickPanelController.handleAutoclickTypeChange( + AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK); + + // 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); + mTestableLooper.processAllMessages(); + + // Verify right click sent. + assertThat(motionEventCaptor.downEvent).isNotNull(); + assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo( + MotionEvent.BUTTON_SECONDARY); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void hoverOnAutoclickPanel_rightClickType_forceTriggerLeftClick() { + MotionEventCaptor motionEventCaptor = new MotionEventCaptor(); + mController.setNext(motionEventCaptor); + + injectFakeMouseActionHoverMoveEvent(); + // Set delay to zero so click is scheduled to run immediately. + mController.mClickScheduler.updateDelay(0); + + // Set click type to right click. + mController.clickPanelController.handleAutoclickTypeChange( + AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK); + // Set mouse to hover panel. + AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class); + when(mockAutoclickTypePanel.isHovered()).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); + mTestableLooper.processAllMessages(); + + // Verify left click is sent due to the mouse hovering the panel. + assertThat(motionEventCaptor.downEvent).isNotNull(); + assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo( + MotionEvent.BUTTON_PRIMARY); + } + private void injectFakeMouseActionHoverMoveEvent() { MotionEvent event = getFakeMotionHoverMoveEvent(); event.setSource(InputDevice.SOURCE_MOUSE); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java index f445b50c7d9c..02361ff259c2 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java @@ -28,7 +28,12 @@ import android.content.Context; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; +import android.view.MotionEvent; +import android.view.View; import android.view.WindowManager; +import android.widget.ImageButton; + +import com.android.internal.R; import org.junit.Before; import org.junit.Rule; @@ -49,12 +54,31 @@ public class AutoclickScrollPanelTest { new TestableContext(getInstrumentation().getContext()); @Mock private WindowManager mMockWindowManager; + @Mock private AutoclickScrollPanel.ScrollPanelControllerInterface mMockScrollPanelController; + private AutoclickScrollPanel mScrollPanel; + // Scroll panel buttons. + private ImageButton mUpButton; + private ImageButton mDownButton; + private ImageButton mLeftButton; + private ImageButton mRightButton; + private ImageButton mExitButton; + @Before public void setUp() { mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager); - mScrollPanel = new AutoclickScrollPanel(mTestableContext, mMockWindowManager); + mScrollPanel = new AutoclickScrollPanel(mTestableContext, mMockWindowManager, + mMockScrollPanelController); + + View contentView = mScrollPanel.getContentViewForTesting(); + + // Initialize buttons. + mUpButton = contentView.findViewById(R.id.scroll_up); + mDownButton = contentView.findViewById(R.id.scroll_down); + mLeftButton = contentView.findViewById(R.id.scroll_left); + mRightButton = contentView.findViewById(R.id.scroll_right); + mExitButton = contentView.findViewById(R.id.scroll_exit); } @Test @@ -89,4 +113,55 @@ public class AutoclickScrollPanelTest { // Verify scroll panel is hidden. assertThat(mScrollPanel.isVisible()).isFalse(); } + + @Test + public void initialState_correctButtonVisibility() { + // Verify all expected buttons exist in the view. + assertThat(mUpButton.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mDownButton.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mLeftButton.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mRightButton.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mExitButton.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void directionButtons_onHover_callsHandleScroll() { + // Test up button. + triggerHoverEvent(mUpButton); + verify(mMockScrollPanelController).handleScroll(AutoclickScrollPanel.DIRECTION_UP); + + // Test down button. + triggerHoverEvent(mDownButton); + verify(mMockScrollPanelController).handleScroll(AutoclickScrollPanel.DIRECTION_DOWN); + + // Test left button. + triggerHoverEvent(mLeftButton); + verify(mMockScrollPanelController).handleScroll(AutoclickScrollPanel.DIRECTION_LEFT); + + // Test right button. + triggerHoverEvent(mRightButton); + verify(mMockScrollPanelController).handleScroll(AutoclickScrollPanel.DIRECTION_RIGHT); + } + + @Test + public void exitButton_onHover_callsExitScrollMode() { + // Test exit button. + triggerHoverEvent(mExitButton); + verify(mMockScrollPanelController).exitScrollMode(); + } + + // Helper method to simulate a hover event on a view. + private void triggerHoverEvent(View view) { + MotionEvent event = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 0, + /* action= */ MotionEvent.ACTION_HOVER_ENTER, + /* x= */ 0, + /* y= */ 0, + /* metaState= */ 0); + + // Dispatch the event to the view's OnHoverListener. + view.dispatchGenericMotionEvent(event); + event.recycle(); + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java index 1af59daa9c78..5922b12edc1e 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java @@ -37,6 +37,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -46,6 +47,7 @@ import android.content.Context; import android.graphics.PointF; import android.os.Looper; import android.os.SystemClock; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.testing.DexmakerShareClassLoaderRule; @@ -504,6 +506,36 @@ public class TouchExplorerTest { assertThat(sentRawEvent.getDisplayId()).isEqualTo(rawDisplayId); } + @Test + @DisableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION) + public void handleMotionEventStateTouchExploring_pointerUp_doesNotSendToManager() { + mTouchExplorer.getState().setServiceDetectsGestures(true); + mTouchExplorer.getState().clear(); + + mLastEvent = pointerDownEvent(); + mTouchExplorer.getState().startTouchExploring(); + MotionEvent event = fromTouchscreen(pointerUpEvent()); + + mTouchExplorer.onMotionEvent(event, event, /*policyFlags=*/0); + + verify(mMockAms, never()).sendMotionEventToListeningServices(event); + } + + @Test + @EnableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION) + public void handleMotionEventStateTouchExploring_pointerUp_sendsToManager() { + mTouchExplorer.getState().setServiceDetectsGestures(true); + mTouchExplorer.getState().clear(); + + mLastEvent = pointerDownEvent(); + mTouchExplorer.getState().startTouchExploring(); + MotionEvent event = fromTouchscreen(pointerUpEvent()); + + mTouchExplorer.onMotionEvent(event, event, /*policyFlags=*/0); + + verify(mMockAms).sendMotionEventToListeningServices(event); + } + /** * Used to play back event data of a gesture by parsing the log into MotionEvents and sending * them to TouchExplorer. diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchStateTest.java new file mode 100644 index 000000000000..3e7d9fd05327 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchStateTest.java @@ -0,0 +1,77 @@ +/* + * 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.accessibility.gestures; + +import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_END; + +import static com.android.server.accessibility.gestures.TouchState.STATE_CLEAR; +import static com.android.server.accessibility.gestures.TouchState.STATE_TOUCH_EXPLORING; + +import static com.google.common.truth.Truth.assertThat; + +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.view.Display; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.accessibility.AccessibilityManagerService; +import com.android.server.accessibility.Flags; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +@RunWith(AndroidJUnit4.class) +public class TouchStateTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private TouchState mTouchState; + @Mock private AccessibilityManagerService mMockAms; + + @Before + public void setup() { + mTouchState = new TouchState(Display.DEFAULT_DISPLAY, mMockAms); + } + + @EnableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION) + @Test + public void injectedEvent_interactionEnd_pointerDown_startsTouchExploring() { + mTouchState.mReceivedPointerTracker.mReceivedPointersDown = 1; + mTouchState.onInjectedAccessibilityEvent(TYPE_TOUCH_INTERACTION_END); + assertThat(mTouchState.getState()).isEqualTo(STATE_TOUCH_EXPLORING); + } + + @EnableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION) + @Test + public void injectedEvent_interactionEnd_pointerUp_clears() { + mTouchState.mReceivedPointerTracker.mReceivedPointersDown = 0; + mTouchState.onInjectedAccessibilityEvent(TYPE_TOUCH_INTERACTION_END); + assertThat(mTouchState.getState()).isEqualTo(STATE_CLEAR); + } + + @DisableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION) + @Test + public void injectedEvent_interactionEnd_clears() { + mTouchState.onInjectedAccessibilityEvent(TYPE_TOUCH_INTERACTION_END); + assertThat(mTouchState.getState()).isEqualTo(STATE_CLEAR); + } +} 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 b0ffebb973a1..aa1d5835bfc8 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -2295,6 +2295,38 @@ public class HdmiCecLocalDeviceTvTest { .hasSize(1); } + @Test + public void onOneTouchPlay_wakeUp_exist_device() { + HdmiCecMessage requestActiveSource = + HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); + + // Go to standby to trigger RequestActiveSourceAction for playback_1 + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + + // Skip the LauncherX API timeout. + mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); + mNativeWrapper.clearResultMessages(); + + // turn off TV and wake up with one touch play + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + + // FakePowerManagerWrapper#wakeUp() doesn't broadcast Intent.ACTION_SCREEN_ON + // manually trigger onWakeUp to mock OTP + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestActiveSource); + } + @Test public void handleReportAudioStatus_SamOnAvrStandby_startSystemAudioActionFromTv() { 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 3ca019728c2b..fcdf88f16550 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -605,29 +605,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { } @Test - public void testKeyGestureAccessibilityShortcutChord() { - Assert.assertTrue( - sendKeyGestureEventStart( - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD)); - mPhoneWindowManager.moveTimeForward(5000); - Assert.assertTrue( - sendKeyGestureEventCancel( - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD)); - mPhoneWindowManager.assertAccessibilityKeychordCalled(); - } - - @Test - public void testKeyGestureAccessibilityShortcutChordCancelled() { - Assert.assertTrue( - sendKeyGestureEventStart( - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD)); - Assert.assertTrue( - sendKeyGestureEventCancel( - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD)); - mPhoneWindowManager.assertAccessibilityKeychordNotCalled(); - } - - @Test public void testKeyGestureRingerToggleChord() { mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE); Assert.assertTrue( @@ -670,29 +647,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { } @Test - public void testKeyGestureAccessibilityTvShortcutChord() { - Assert.assertTrue( - sendKeyGestureEventStart( - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD)); - mPhoneWindowManager.moveTimeForward(5000); - Assert.assertTrue( - sendKeyGestureEventCancel( - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD)); - mPhoneWindowManager.assertAccessibilityKeychordCalled(); - } - - @Test - public void testKeyGestureAccessibilityTvShortcutChordCancelled() { - Assert.assertTrue( - sendKeyGestureEventStart( - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD)); - Assert.assertTrue( - sendKeyGestureEventCancel( - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD)); - mPhoneWindowManager.assertAccessibilityKeychordNotCalled(); - } - - @Test public void testKeyGestureTvTriggerBugReport() { Assert.assertTrue( sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT)); diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index f88492477487..e56fd3c6272d 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -750,11 +750,6 @@ class TestPhoneWindowManager { verify(mAccessibilityShortcutController).performAccessibilityShortcut(); } - void assertAccessibilityKeychordNotCalled() { - mTestLooper.dispatchAll(); - verify(mAccessibilityShortcutController, never()).performAccessibilityShortcut(); - } - void assertCloseAllDialogs() { verify(mContext).closeSystemDialogs(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index e3e9cc426bb3..08b0077c49b3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -797,7 +797,7 @@ public class ActivityStarterTests extends WindowTestsBase { // Create adjacent tasks and put one activity under it final Task parent = new TaskBuilder(mSupervisor).build(); final Task adjacentParent = new TaskBuilder(mSupervisor).build(); - parent.setAdjacentTaskFragment(adjacentParent); + parent.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(parent, adjacentParent)); final ActivityRecord activity = new ActivityBuilder(mAtm) .setParentTask(parent) .setCreateTask(true).build(); 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 a9be47d71213..86d901b640ff 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java @@ -488,14 +488,13 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false); final TaskFragment tf2 = createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false); - tf1.setAdjacentTaskFragment(tf2); + tf1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf1, tf2)); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue(); } @Test - @EnableFlags({Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, - Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS}) + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void testOpaque_rootTask_nonFillingOpaqueAdjacentChildren_multipleAdjacent_isOpaque() { final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); final TaskFragment tf1 = createChildTaskFragment(/* parent */ rootTask, 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 8fe08553db95..cb98b9a490d8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java @@ -243,6 +243,11 @@ class AppCompatActivityRobot { .getAspectRatioOverrides()).getUserMinAspectRatio(); } + void setShouldRefreshActivityForCameraCompat(boolean enabled) { + doReturn(enabled).when(mActivityStack.top().mAppCompatController.getCameraOverrides()) + .shouldRefreshActivityForCameraCompat(); + } + void setIgnoreOrientationRequest(boolean enabled) { mDisplayContent.setIgnoreOrientationRequest(enabled); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java index 05f6ed644632..7ef85262dfc2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java @@ -64,6 +64,19 @@ class AppCompatConfigurationRobot { .isCameraCompatTreatmentEnabledAtBuildTime(); } + void setCameraCompatAspectRatio(float aspectRatio) { + doReturn(aspectRatio).when(mAppCompatConfiguration).getCameraCompatAspectRatio(); + } + + void enableCameraCompatRefresh(boolean enabled) { + doReturn(enabled).when(mAppCompatConfiguration).isCameraCompatRefreshEnabled(); + } + + void enableCameraCompatRefreshCycleThroughStop(boolean enabled) { + doReturn(enabled).when(mAppCompatConfiguration) + .isCameraCompatRefreshCycleThroughStopEnabled(); + } + void enableUserAppAspectRatioFullscreen(boolean enabled) { doReturn(enabled).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled(); } 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 bdee3c323549..dd3e9fcbbdaf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -343,8 +343,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { // Adjacent + no companion => unable to predict // TF1 | TF2 - tf1.setAdjacentTaskFragment(tf2); - tf2.setAdjacentTaskFragment(tf1); + tf1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf1, tf2)); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.isEmpty()); @@ -393,8 +392,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { // Adjacent => predict for previous activity. // TF2 | TF3 // TF1 - tf2.setAdjacentTaskFragment(tf3); - tf3.setAdjacentTaskFragment(tf2); + tf2.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf2, tf3)); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.contains(prevAr)); @@ -657,8 +655,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { final TaskFragment secondaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer); final ActivityRecord primaryActivity = primaryTf.getTopMostActivity(); final ActivityRecord secondaryActivity = secondaryTf.getTopMostActivity(); - primaryTf.setAdjacentTaskFragment(secondaryTf); - secondaryTf.setAdjacentTaskFragment(primaryTf); + primaryTf.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(primaryTf, secondaryTf)); final WindowState primaryWindow = mock(WindowState.class); final WindowState secondaryWindow = mock(WindowState.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java index f5bec04a98d5..6f959812d742 100644 --- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java @@ -21,13 +21,13 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE_ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT; +import static android.app.CameraCompatTaskInfo.FreeformCameraCompatMode; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT; -import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; @@ -40,18 +40,15 @@ import static android.view.Surface.ROTATION_90; 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.spyOn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING; import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -59,13 +56,11 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.annotation.NonNull; -import android.app.CameraCompatTaskInfo; import android.app.IApplicationThread; import android.app.WindowConfiguration.WindowingMode; import android.app.servertransaction.RefreshCallbackItem; import android.app.servertransaction.ResumeActivityItem; import android.compat.testing.PlatformCompatChangeRule; -import android.content.ComponentName; import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -73,17 +68,16 @@ import android.content.res.Configuration.Orientation; import android.graphics.Rect; import android.hardware.camera2.CameraManager; import android.os.Handler; +import android.os.RemoteException; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; -import android.view.DisplayInfo; import android.view.Surface; import androidx.test.filters.SmallTest; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; @@ -91,6 +85,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Tests for {@link CameraCompatFreeformPolicy}. @@ -109,30 +104,18 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { private static final String TEST_PACKAGE_1 = "com.android.frameworks.wmtests"; private static final String TEST_PACKAGE_2 = "com.test.package.two"; private static final String CAMERA_ID_1 = "camera-1"; - private AppCompatConfiguration mAppCompatConfiguration; - - private CameraManager.AvailabilityCallback mCameraAvailabilityCallback; - private CameraCompatFreeformPolicy mCameraCompatFreeformPolicy; - private ActivityRecord mActivity; - - // TODO(b/384465100): use a robot structure. - @Before - public void setUp() throws Exception { - setupAppCompatConfiguration(); - setupCameraManager(); - setupHandler(); - doReturn(true).when(() -> DesktopModeHelper.canEnterDesktopMode(any())); - } @Test @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testFeatureDisabled_cameraCompatFreeformPolicyNotCreated() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertNull(mCameraCompatFreeformPolicy); + robot.checkCameraCompatPolicyNotCreated(); + }); } @Test @@ -140,31 +123,37 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT}) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION}) public void testIsCameraRunningAndWindowingModeEligible_disabledViaOverride_returnsFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity)); + robot.checkIsCameraRunningAndWindowingModeEligible(false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testIsCameraRunningAndWindowingModeEligible_cameraNotRunning_returnsFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity)); + robot.checkIsCameraRunningAndWindowingModeEligible(false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testIsCameraRunningAndWindowingModeEligible_notFreeformWindowing_returnsFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity)); + robot.checkIsCameraRunningAndWindowingModeEligible(false); + }); } @Test @@ -172,64 +161,76 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testIsCameraRunningAndWindowingModeEligible_optInFreeformCameraRunning_true() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertTrue(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity)); + robot.checkIsCameraRunningAndWindowingModeEligible(true); + }); } @Test @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT}) public void testIsCameraRunningAndWindowingModeEligible_freeformCameraRunning_true() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertTrue(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity)); + robot.checkIsCameraRunningAndWindowingModeEligible(true); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT) public void testIsFreeformLetterboxingForCameraAllowed_optInMechanism_notOptedIn_retFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity)); + robot.checkIsFreeformLetterboxingForCameraAllowed(false); + }); } @Test @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT}) public void testIsFreeformLetterboxingForCameraAllowed_notOptedOut_returnsTrue() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertTrue(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity)); + robot.checkIsFreeformLetterboxingForCameraAllowed(true); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testIsFreeformLetterboxingForCameraAllowed_cameraNotRunning_returnsFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity)); + robot.checkIsFreeformLetterboxingForCameraAllowed(false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testIsFreeformLetterboxingForCameraAllowed_notFreeformWindowing_returnsFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity)); + robot.checkIsFreeformLetterboxingForCameraAllowed(false); + }); } @Test @@ -237,519 +238,603 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testIsFreeformLetterboxingForCameraAllowed_optInFreeformCameraRunning_true() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertTrue(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity)); + robot.checkIsFreeformLetterboxingForCameraAllowed(true); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testFullscreen_doesNotActivateCameraCompatMode() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); - doReturn(false).when(mActivity).inFreeformWindowingMode(); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); + robot.setInFreeformWindowingMode(false); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertNotInCameraCompatMode(); + robot.assertNotInCameraCompatMode(); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testOrientationUnspecified_doesNotActivateCameraCompatMode() { - configureActivity(SCREEN_ORIENTATION_UNSPECIFIED); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_UNSPECIFIED); - assertNotInCameraCompatMode(); + robot.assertNotInCameraCompatMode(); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testNoCameraConnection_doesNotActivateCameraCompatMode() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - assertNotInCameraCompatMode(); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + + robot.assertNotInCameraCompatMode(); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() throws Exception { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - setDisplayRotation(ROTATION_0); + public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.activity().rotateDisplayForTopActivity(ROTATION_0); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT); - assertActivityRefreshRequested(/* refreshRequested */ false); + robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT); + robot.assertActivityRefreshRequested(/* refreshRequested */ false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() throws Exception { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - setDisplayRotation(ROTATION_270); + public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.activity().rotateDisplayForTopActivity(ROTATION_270); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); - assertActivityRefreshRequested(/* refreshRequested */ false); + robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); + robot.assertActivityRefreshRequested(/* refreshRequested */ false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() throws Exception { - configureActivity(SCREEN_ORIENTATION_LANDSCAPE); - setDisplayRotation(ROTATION_0); + public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_LANDSCAPE); + robot.activity().rotateDisplayForTopActivity(ROTATION_0); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT); - assertActivityRefreshRequested(/* refreshRequested */ false); + robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT); + robot.assertActivityRefreshRequested(/* refreshRequested */ false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() throws Exception { - configureActivity(SCREEN_ORIENTATION_LANDSCAPE); - setDisplayRotation(ROTATION_270); + public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_LANDSCAPE); + robot.activity().rotateDisplayForTopActivity(ROTATION_270); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE); - assertActivityRefreshRequested(/* refreshRequested */ false); + robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE); + robot.assertActivityRefreshRequested(/* refreshRequested */ false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testCameraReconnected_cameraCompatModeAndRefresh() throws Exception { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - setDisplayRotation(ROTATION_270); + public void testCameraReconnected_cameraCompatModeAndRefresh() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.activity().rotateDisplayForTopActivity(ROTATION_270); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true, + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.callOnActivityConfigurationChanging(/* letterboxNew= */ true, /* lastLetterbox= */ false); - assertActivityRefreshRequested(/* refreshRequested */ true); - onCameraClosed(CAMERA_ID_1); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - // Activity is letterboxed from the previous configuration change. - callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true, - /* lastLetterbox= */ true); + robot.assertActivityRefreshRequested(/* refreshRequested */ true); + robot.onCameraClosed(CAMERA_ID_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + // Activity is letterboxed from the previous configuration change. + robot.callOnActivityConfigurationChanging(/* letterboxNew= */ true, + /* lastLetterbox= */ true); - assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); - assertActivityRefreshRequested(/* refreshRequested */ true); + robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); + robot.assertActivityRefreshRequested(/* refreshRequested */ true); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testCameraOpenedForDifferentPackage_notInCameraCompatMode() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2); - assertNotInCameraCompatMode(); + robot.assertNotInCameraCompatMode(); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT) public void testShouldApplyCameraCompatFreeformTreatment_overrideNotEnabled_returnsFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertFalse(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity, - /* checkOrientation */ true)); + robot.checkIsCameraCompatTreatmentActiveForTopActivity(false); + }); } @Test @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT}) public void testShouldApplyCameraCompatFreeformTreatment_notOptedOut_returnsTrue() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertTrue(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity, - /* checkOrientation */ true)); + robot.checkIsCameraCompatTreatmentActiveForTopActivity(true); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT) public void testShouldApplyCameraCompatFreeformTreatment_enabledByOverride_returnsTrue() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertTrue(mActivity.info - .isChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)); - assertTrue(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity, - /* checkOrientation */ true)); + robot.checkIsCameraCompatTreatmentActiveForTopActivity(true); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testShouldRefreshActivity_appBoundsChanged_returnsTrue() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - Configuration oldConfiguration = createConfiguration(/* letterbox= */ false); - Configuration newConfiguration = createConfiguration(/* letterbox= */ true); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration, - oldConfiguration)); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + + robot.checkShouldRefreshActivity(/* expected= */ true, + robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0), + robot.createConfiguration(/* letterbox= */ false, /* rotation= */ 0)); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testShouldRefreshActivity_displayRotationChanged_returnsTrue() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - Configuration oldConfiguration = createConfiguration(/* letterbox= */ true); - Configuration newConfiguration = createConfiguration(/* letterbox= */ true); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - oldConfiguration.windowConfiguration.setDisplayRotation(0); - newConfiguration.windowConfiguration.setDisplayRotation(90); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration, - oldConfiguration)); + robot.checkShouldRefreshActivity(/* expected= */ true, + robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 90), + robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0)); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testShouldRefreshActivity_appBoundsNorDisplayChanged_returnsFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - Configuration oldConfiguration = createConfiguration(/* letterbox= */ true); - Configuration newConfiguration = createConfiguration(/* letterbox= */ true); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - oldConfiguration.windowConfiguration.setDisplayRotation(0); - newConfiguration.windowConfiguration.setDisplayRotation(0); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertFalse(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration, - oldConfiguration)); + robot.checkShouldRefreshActivity(/* expected= */ false, + robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0), + robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0)); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh() - throws Exception { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - - doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides()) - .shouldRefreshActivityForCameraCompat(); + public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.activity().setShouldRefreshActivityForCameraCompat(false); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - callOnActivityConfigurationChanging(mActivity); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.callOnActivityConfigurationChanging(); - assertActivityRefreshRequested(/* refreshRequested */ false); + robot.assertActivityRefreshRequested(/* refreshRequested */ false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception { - when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) - .thenReturn(false); + public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.conf().enableCameraCompatRefreshCycleThroughStop(false); - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.callOnActivityConfigurationChanging(); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - callOnActivityConfigurationChanging(mActivity); - - assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); + robot.assertActivityRefreshRequested(/* refreshRequested */ true, + /* cycleThroughStop */ false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp() - throws Exception { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides()) - .shouldRefreshActivityViaPauseForCameraCompat(); + public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.setShouldRefreshActivityViaPause(true); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - callOnActivityConfigurationChanging(mActivity); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.callOnActivityConfigurationChanging(); - assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); + robot.assertActivityRefreshRequested(/* refreshRequested */ true, + /* cycleThroughStop */ false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testGetCameraCompatAspectRatio_activityNotInCameraCompat_returnsDefaultAspRatio() { - configureActivity(SCREEN_ORIENTATION_FULL_USER); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_FULL_USER); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - callOnActivityConfigurationChanging(mActivity); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.callOnActivityConfigurationChanging(); - assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO, - mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity), - /* delta= */ 0.001); + robot.checkCameraCompatAspectRatioEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testGetCameraCompatAspectRatio_activityInCameraCompat_returnsConfigAspectRatio() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - final float configAspectRatio = 1.5f; - mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + final float configAspectRatio = 1.5f; + robot.conf().setCameraCompatAspectRatio(configAspectRatio); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - callOnActivityConfigurationChanging(mActivity); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.callOnActivityConfigurationChanging(); - assertEquals(configAspectRatio, - mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity), - /* delta= */ 0.001); + robot.checkCameraCompatAspectRatioEquals(configAspectRatio); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testGetCameraCompatAspectRatio_inCameraCompatPerAppOverride_returnDefAspectRatio() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - final float configAspectRatio = 1.5f; - mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio); - doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides()) - .isOverrideMinAspectRatioForCameraEnabled(); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.conf().setCameraCompatAspectRatio(1.5f); + robot.setOverrideMinAspectRatioEnabled(true); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - callOnActivityConfigurationChanging(mActivity); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.callOnActivityConfigurationChanging(); - assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO, - mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity), - /* delta= */ 0.001); + robot.checkCameraCompatAspectRatioEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testOnCameraOpened_portraitActivity_sandboxesDisplayRotationAndUpdatesApp() throws - Exception { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - setDisplayRotation(ROTATION_270); + public void testOnCameraOpened_portraitActivity_sandboxesDisplayRotationAndUpdatesApp() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.activity().rotateDisplayForTopActivity(ROTATION_270); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - // This is a portrait rotation for a device with portrait natural orientation (most common, - // currently the only one supported). - assertCompatibilityInfoSentWithDisplayRotation(ROTATION_0); + // This is a portrait rotation for a device with portrait natural orientation (most + // common, currently the only one supported). + robot.assertCompatibilityInfoSentWithDisplayRotation(ROTATION_0); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testOnCameraOpened_landscapeActivity_sandboxesDisplayRotationAndUpdatesApp() throws - Exception { - configureActivity(SCREEN_ORIENTATION_LANDSCAPE); - setDisplayRotation(ROTATION_0); - - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - - // This is a landscape rotation for a device with portrait natural orientation (most common, - // currently the only one supported). - assertCompatibilityInfoSentWithDisplayRotation(ROTATION_90); - } - - private void setupAppCompatConfiguration() { - mAppCompatConfiguration = mDisplayContent.mWmService.mAppCompatConfiguration; - spyOn(mAppCompatConfiguration); - when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()).thenReturn(true); - when(mAppCompatConfiguration.isCameraCompatTreatmentEnabledAtBuildTime()).thenReturn(true); - when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()).thenReturn(true); - when(mAppCompatConfiguration.isCameraCompatSplitScreenAspectRatioEnabled()) - .thenReturn(false); - when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) - .thenReturn(true); - } - - private void setupCameraManager() { - final CameraManager mockCameraManager = mock(CameraManager.class); - doAnswer(invocation -> { - mCameraAvailabilityCallback = invocation.getArgument(1); - return null; - }).when(mockCameraManager).registerAvailabilityCallback( - any(Executor.class), any(CameraManager.AvailabilityCallback.class)); - - when(mContext.getSystemService(CameraManager.class)).thenReturn(mockCameraManager); - } - - private void setupHandler() { - final Handler handler = mDisplayContent.mWmService.mH; - spyOn(handler); - - when(handler.postDelayed(any(Runnable.class), anyLong())).thenAnswer( - invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }); - } - - private void configureActivity(@ScreenOrientation int activityOrientation) { - configureActivity(activityOrientation, WINDOWING_MODE_FREEFORM); - } - - private void configureActivity(@ScreenOrientation int activityOrientation, - @WindowingMode int windowingMode) { - configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT, windowingMode); - } - - private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation, - @Orientation int naturalOrientation, @WindowingMode int windowingMode) { - setupDisplayContent(naturalOrientation); - final Task task = setupTask(windowingMode); - setupActivity(task, activityOrientation, windowingMode); - setupMockApplicationThread(); - - mCameraCompatFreeformPolicy = mDisplayContent.mAppCompatCameraPolicy - .mCameraCompatFreeformPolicy; - } - - private void setupDisplayContent(@Orientation int naturalOrientation) { - // Create a new DisplayContent so that the flag values create the camera freeform policy. - mDisplayContent = new TestDisplayContent.Builder(mAtm, mDisplayContent.getSurfaceWidth(), - mDisplayContent.getSurfaceHeight()).build(); - mDisplayContent.setIgnoreOrientationRequest(true); - setDisplayRotation(ROTATION_90); - doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation(); - } - - private Task setupTask(@WindowingMode int windowingMode) { - final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea(); - spyOn(tda); - doReturn(true).when(tda).supportsNonResizableMultiWindow(); - - final Task task = new TaskBuilder(mSupervisor) - .setDisplay(mDisplayContent) - .setWindowingMode(windowingMode) - .build(); - task.setBounds(0, 0, 1000, 500); - return task; - } - - private void setupActivity(@NonNull Task task, @ScreenOrientation int activityOrientation, - @WindowingMode int windowingMode) { - mActivity = new ActivityBuilder(mAtm) - // Set the component to be that of the test class in order to enable compat changes - .setComponent(ComponentName.createRelative(mContext, - com.android.server.wm.CameraCompatFreeformPolicyTests.class.getName())) - .setScreenOrientation(activityOrientation) - .setResizeMode(RESIZE_MODE_RESIZEABLE) - .setCreateTask(true) - .setOnTop(true) - .setTask(task) - .build(); - mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode(); - - spyOn(mActivity.mAppCompatController.getCameraOverrides()); - spyOn(mActivity.info); - - doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean()); - doReturn(windowingMode == WINDOWING_MODE_FREEFORM).when(mActivity) - .inFreeformWindowingMode(); - } - - private void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) { - mCameraAvailabilityCallback.onCameraOpened(cameraId, packageName); - waitHandlerIdle(mDisplayContent.mWmService.mH); - } - - private void onCameraClosed(@NonNull String cameraId) { - mCameraAvailabilityCallback.onCameraClosed(cameraId); - waitHandlerIdle(mDisplayContent.mWmService.mH); - } - - private void assertInCameraCompatMode(@CameraCompatTaskInfo.FreeformCameraCompatMode int mode) { - assertEquals(mode, mCameraCompatFreeformPolicy.getCameraCompatMode(mActivity)); - } - - private void assertNotInCameraCompatMode() { - assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_NONE); - } - - private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception { - assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true); - } - - private void assertActivityRefreshRequested(boolean refreshRequested, - boolean cycleThroughStop) throws Exception { - verify(mActivity.mAppCompatController.getCameraOverrides(), - times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true); - - final RefreshCallbackItem refreshCallbackItem = - new RefreshCallbackItem(mActivity.token, cycleThroughStop ? ON_STOP : ON_PAUSE); - final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(mActivity.token, - /* isForward */ false, /* shouldSendCompatFakeFocus */ false); - - verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0)) - .scheduleTransactionItems(mActivity.app.getThread(), - refreshCallbackItem, resumeActivityItem); - } - - private void callOnActivityConfigurationChanging(ActivityRecord activity) { - callOnActivityConfigurationChanging(activity, /* letterboxNew= */ true, - /* lastLetterbox= */false); - } - - private void callOnActivityConfigurationChanging(ActivityRecord activity, boolean letterboxNew, - boolean lastLetterbox) { - mDisplayContent.mAppCompatCameraPolicy.mActivityRefresher - .onActivityConfigurationChanging(activity, - /* newConfig */ createConfiguration(letterboxNew), - /* lastReportedConfig */ createConfiguration(lastLetterbox)); - } - - private Configuration createConfiguration(boolean letterbox) { - final Configuration configuration = new Configuration(); - Rect bounds = letterbox ? new Rect(/*left*/ 300, /*top*/ 0, /*right*/ 700, /*bottom*/ 600) - : new Rect(/*left*/ 0, /*top*/ 0, /*right*/ 1000, /*bottom*/ 600); - configuration.windowConfiguration.setAppBounds(bounds); - return configuration; - } - - private void setDisplayRotation(@Surface.Rotation int displayRotation) { - doAnswer(invocation -> { - DisplayInfo displayInfo = new DisplayInfo(); - mDisplayContent.getDisplay().getDisplayInfo(displayInfo); - displayInfo.rotation = displayRotation; - // Set height so that the natural orientation (rotation is 0) is portrait. This is the - // case for most standard phones and tablets. - // TODO(b/365725400): handle landscape natural orientation. - displayInfo.logicalHeight = displayRotation % 180 == 0 ? 800 : 600; - displayInfo.logicalWidth = displayRotation % 180 == 0 ? 600 : 800; - return displayInfo; - }).when(mDisplayContent.mWmService.mDisplayManagerInternal) - .getDisplayInfo(anyInt()); - } - - private void setupMockApplicationThread() { - IApplicationThread mockApplicationThread = mock(IApplicationThread.class); - spyOn(mActivity.app); - doReturn(mockApplicationThread).when(mActivity.app).getThread(); - } - - private void assertCompatibilityInfoSentWithDisplayRotation(@Surface.Rotation int - expectedRotation) throws Exception { - final ArgumentCaptor<CompatibilityInfo> compatibilityInfoArgumentCaptor = - ArgumentCaptor.forClass(CompatibilityInfo.class); - verify(mActivity.app.getThread()).updatePackageCompatibilityInfo(eq(mActivity.packageName), - compatibilityInfoArgumentCaptor.capture()); - - final CompatibilityInfo compatInfo = compatibilityInfoArgumentCaptor.getValue(); - assertTrue(compatInfo.isOverrideDisplayRotationRequired()); - assertEquals(expectedRotation, compatInfo.applicationDisplayRotation); + public void testOnCameraOpened_landscapeActivity_sandboxesDisplayRotationAndUpdatesApp() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_LANDSCAPE); + robot.activity().rotateDisplayForTopActivity(ROTATION_0); + + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + + // This is a landscape rotation for a device with portrait natural orientation (most + // common, currently the only one supported). + robot.assertCompatibilityInfoSentWithDisplayRotation(ROTATION_90); + }); + } + + /** + * Runs a test scenario providing a Robot. + */ + void runTestScenario(@NonNull Consumer<CameraCompatFreeformPolicyRobotTests> consumer) { + final CameraCompatFreeformPolicyRobotTests robot = + new CameraCompatFreeformPolicyRobotTests(mWm, mAtm, mSupervisor, this); + consumer.accept(robot); + } + + private static class CameraCompatFreeformPolicyRobotTests extends AppCompatRobotBase { + private final WindowTestsBase mWindowTestsBase; + + private CameraManager.AvailabilityCallback mCameraAvailabilityCallback; + + CameraCompatFreeformPolicyRobotTests(@NonNull WindowManagerService wm, + @NonNull ActivityTaskManagerService atm, + @NonNull ActivityTaskSupervisor supervisor, + @NonNull WindowTestsBase windowTestsBase) { + super(wm, atm, supervisor); + mWindowTestsBase = windowTestsBase; + setupCameraManager(); + setupAppCompatConfiguration(); + } + + @Override + void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) { + super.onPostDisplayContentCreation(displayContent); + spyOn(displayContent.mAppCompatCameraPolicy); + if (displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy != null) { + spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy); + } + } + + @Override + void onPostActivityCreation(@NonNull ActivityRecord activity) { + super.onPostActivityCreation(activity); + setupCameraManager(); + setupHandler(); + setupMockApplicationThread(); + } + + private void setupMockApplicationThread() { + IApplicationThread mockApplicationThread = mock(IApplicationThread.class); + spyOn(activity().top().app); + doReturn(mockApplicationThread).when(activity().top().app).getThread(); + } + + private Configuration createConfiguration(boolean letterbox, int rotation) { + final Configuration configuration = createConfiguration(letterbox); + configuration.windowConfiguration.setDisplayRotation(rotation); + return configuration; + } + + private Configuration createConfiguration(boolean letterbox) { + final Configuration configuration = new Configuration(); + Rect bounds = letterbox ? new Rect(/*left*/ 300, /*top*/ 0, /*right*/ 700, /*bottom*/ + 600) + : new Rect(/*left*/ 0, /*top*/ 0, /*right*/ 1000, /*bottom*/ 600); + configuration.windowConfiguration.setAppBounds(bounds); + return configuration; + } + + private void setupAppCompatConfiguration() { + applyOnConf((c) -> { + c.enableCameraCompatTreatment(true); + c.enableCameraCompatTreatmentAtBuildTime(true); + c.enableCameraCompatRefresh(true); + c.enableCameraCompatRefreshCycleThroughStop(true); + c.enableCameraCompatSplitScreenAspectRatio(false); + }); + } + + private void setupCameraManager() { + final CameraManager mockCameraManager = mock(CameraManager.class); + doAnswer(invocation -> { + mCameraAvailabilityCallback = invocation.getArgument(1); + return null; + }).when(mockCameraManager).registerAvailabilityCallback( + any(Executor.class), any(CameraManager.AvailabilityCallback.class)); + + doReturn(mockCameraManager).when(mWindowTestsBase.mWm.mContext).getSystemService( + CameraManager.class); + } + + private void setupHandler() { + final Handler handler = activity().top().mWmService.mH; + spyOn(handler); + + doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(handler).postDelayed(any(Runnable.class), anyLong()); + } + + private void configureActivity(@ScreenOrientation int activityOrientation) { + configureActivity(activityOrientation, WINDOWING_MODE_FREEFORM); + } + + private void configureActivity(@ScreenOrientation int activityOrientation, + @WindowingMode int windowingMode) { + configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT, windowingMode); + } + + private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation, + @Orientation int naturalOrientation, @WindowingMode int windowingMode) { + applyOnActivity(a -> { + dw().allowEnterDesktopMode(true); + a.createActivityWithComponentInNewTaskAndDisplay(); + a.setIgnoreOrientationRequest(true); + a.rotateDisplayForTopActivity(ROTATION_90); + a.configureTopActivity(/* minAspect */ -1, /* maxAspect */ -1, + activityOrientation, /* isUnresizable */ false); + a.top().setWindowingMode(windowingMode); + a.displayContent().setWindowingMode(windowingMode); + a.setDisplayNaturalOrientation(naturalOrientation); + spyOn(a.top().mAppCompatController.getCameraOverrides()); + spyOn(a.top().info); + doReturn(a.displayContent().getDisplayInfo()).when( + a.displayContent().mWmService.mDisplayManagerInternal).getDisplayInfo( + a.displayContent().mDisplayId); + }); + } + + private void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) { + mCameraAvailabilityCallback.onCameraOpened(cameraId, packageName); + waitHandlerIdle(); + } + + private void onCameraClosed(@NonNull String cameraId) { + mCameraAvailabilityCallback.onCameraClosed(cameraId); + } + + private void waitHandlerIdle() { + mWindowTestsBase.waitHandlerIdle(activity().displayContent().mWmService.mH); + } + + void setInFreeformWindowingMode(boolean inFreeform) { + doReturn(inFreeform).when(activity().top()).inFreeformWindowingMode(); + } + + void setShouldRefreshActivityViaPause(boolean enabled) { + doReturn(enabled).when(activity().top().mAppCompatController.getCameraOverrides()) + .shouldRefreshActivityViaPauseForCameraCompat(); + } + + void checkShouldRefreshActivity(boolean expected, Configuration newConfig, + Configuration oldConfig) { + assertEquals(expected, cameraCompatFreeformPolicy().shouldRefreshActivity( + activity().top(), newConfig, oldConfig)); + } + + void checkCameraCompatPolicyNotCreated() { + assertNull(cameraCompatFreeformPolicy()); + } + + void checkIsCameraRunningAndWindowingModeEligible(boolean expected) { + assertEquals(expected, cameraCompatFreeformPolicy() + .isCameraRunningAndWindowingModeEligible(activity().top())); + } + + void checkIsFreeformLetterboxingForCameraAllowed(boolean expected) { + assertEquals(expected, cameraCompatFreeformPolicy() + .isFreeformLetterboxingForCameraAllowed(activity().top())); + } + + void checkCameraCompatAspectRatioEquals(float aspectRatio) { + assertEquals(aspectRatio, + cameraCompatFreeformPolicy().getCameraCompatAspectRatio(activity().top()), + /* delta= */ 0.001); + } + + private void assertInCameraCompatMode(@FreeformCameraCompatMode int mode) { + assertEquals(mode, cameraCompatFreeformPolicy().getCameraCompatMode(activity().top())); + } + + private void assertNotInCameraCompatMode() { + assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_NONE); + } + + private void assertActivityRefreshRequested(boolean refreshRequested) { + assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true); + } + + private void assertActivityRefreshRequested(boolean refreshRequested, + boolean cycleThroughStop) { + verify(activity().top().mAppCompatController.getCameraOverrides(), + times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true); + + final RefreshCallbackItem refreshCallbackItem = + new RefreshCallbackItem(activity().top().token, + cycleThroughStop ? ON_STOP : ON_PAUSE); + final ResumeActivityItem resumeActivityItem = new ResumeActivityItem( + activity().top().token, + /* isForward */ false, /* shouldSendCompatFakeFocus */ false); + try { + verify(activity().top().mAtmService.getLifecycleManager(), + times(refreshRequested ? 1 : 0)) + .scheduleTransactionItems(activity().top().app.getThread(), + refreshCallbackItem, resumeActivityItem); + } catch (RemoteException e) { + fail(e.getMessage()); + } + } + + private void callOnActivityConfigurationChanging() { + callOnActivityConfigurationChanging(/* letterboxNew= */ true, + /* lastLetterbox= */false); + } + + private void callOnActivityConfigurationChanging(boolean letterboxNew, + boolean lastLetterbox) { + activity().displayContent().mAppCompatCameraPolicy.mActivityRefresher + .onActivityConfigurationChanging(activity().top(), + /* newConfig */ createConfiguration(letterboxNew), + /* lastReportedConfig */ createConfiguration(lastLetterbox)); + } + + void checkIsCameraCompatTreatmentActiveForTopActivity(boolean active) { + assertEquals(active, + cameraCompatFreeformPolicy().isTreatmentEnabledForActivity(activity().top(), + /* checkOrientation */ true)); + } + + void setOverrideMinAspectRatioEnabled(boolean enabled) { + doReturn(enabled).when(activity().top().mAppCompatController.getCameraOverrides()) + .isOverrideMinAspectRatioForCameraEnabled(); + } + + void assertCompatibilityInfoSentWithDisplayRotation(@Surface.Rotation int + expectedRotation) { + final ArgumentCaptor<CompatibilityInfo> compatibilityInfoArgumentCaptor = + ArgumentCaptor.forClass(CompatibilityInfo.class); + try { + verify(activity().top().app.getThread()).updatePackageCompatibilityInfo( + eq(activity().top().packageName), + compatibilityInfoArgumentCaptor.capture()); + } catch (RemoteException e) { + fail(e.getMessage()); + } + + final CompatibilityInfo compatInfo = compatibilityInfoArgumentCaptor.getValue(); + assertTrue(compatInfo.isOverrideDisplayRotationRequired()); + assertEquals(expectedRotation, compatInfo.applicationDisplayRotation); + } + + CameraCompatFreeformPolicy cameraCompatFreeformPolicy() { + return activity().displayContent().mAppCompatCameraPolicy.mCameraCompatFreeformPolicy; + } } } 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 1e91bedb5c18..43755ea3165e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java @@ -181,6 +181,7 @@ public class DesktopModeHelperTest { assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue(); } + @DisableFlags(Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE) @Test public void isDeviceEligibleForDesktopMode_configDEModeOffAndIntDispHostsDesktop_returnsFalse() { doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported)); diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java index bc37496d14a7..e87e107cd793 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE; @@ -157,7 +158,7 @@ public class DesktopModeLaunchParamsModifierTests extends @Test @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX}) - public void testReturnsContinueIfVisibleFreeformTaskExists() { + public void testReturnsContinueIfFreeformTaskExists() { setupDesktopModeLaunchParamsModifier(); when(mTarget.isEnteringDesktopMode(any(), any(), any())).thenCallRealMethod(); @@ -165,7 +166,7 @@ public class DesktopModeLaunchParamsModifierTests extends final Task existingFreeformTask = new TaskBuilder(mSupervisor).setCreateActivity(true) .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); doReturn(existingFreeformTask.getRootActivity()).when(dc) - .getTopMostVisibleFreeformActivity(); + .getTopMostFreeformActivity(); final Task launchingTask = new TaskBuilder(mSupervisor).build(); launchingTask.onDisplayChanged(dc); @@ -269,6 +270,38 @@ public class DesktopModeLaunchParamsModifierTests extends } @Test + @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, + Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES}) + public void testInheritTaskBoundsFromExistingInstanceIfClosing() { + setupDesktopModeLaunchParamsModifier(); + + final String packageName = "com.same.package"; + // Setup existing task. + final DisplayContent dc = spy(createNewDisplay()); + final Task existingFreeformTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FREEFORM).setPackage(packageName).build(); + existingFreeformTask.setBounds( + /* left */ 0, + /* top */ 0, + /* right */ 500, + /* bottom */ 500); + doReturn(existingFreeformTask.getRootActivity()).when(dc) + .getTopMostVisibleFreeformActivity(); + // Set up new instance of already existing task. By default multi instance is not supported + // so first instance will close. + final Task launchingTask = new TaskBuilder(mSupervisor).setPackage(packageName) + .setCreateActivity(true).build(); + launchingTask.onDisplayChanged(dc); + launchingTask.intent.addFlags(FLAG_ACTIVITY_NEW_TASK); + + // New instance should inherit task bounds of old instance. + assertEquals(RESULT_DONE, + new CalculateRequestBuilder().setTask(launchingTask) + .setActivity(launchingTask.getRootActivity()).calculate()); + assertEquals(existingFreeformTask.getBounds(), mResult.mBounds); + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) @DisableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) public void testUsesDesiredBoundsIfEmptyLayoutAndActivityOptionsBounds() { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayCompatTests.java new file mode 100644 index 000000000000..1445a6982c60 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayCompatTests.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019 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.view.Display.DEFAULT_DISPLAY; + +import static com.android.window.flags.Flags.FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS; + +import static junit.framework.Assert.assertFalse; + +import static org.junit.Assert.assertTrue; + +import android.compat.testing.PlatformCompatChangeRule; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; +import android.view.DisplayInfo; + +import androidx.test.filters.MediumTest; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +/** + * Build/Install/Run: + * atest WmTests:DisplayCompatTests + */ +@MediumTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class DisplayCompatTests extends WindowTestsBase { + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + @EnableFlags(FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS) + @Test + public void testFixedMiscConfigurationWhenMovingToDisplay() { + // Create an app on the default display, at which point the restart menu isn't enabled. + final Task task = createTask(mDefaultDisplay); + final ActivityRecord activity = createActivityRecord(task); + assertFalse(task.getTaskInfo().appCompatTaskInfo.isRestartMenuEnabledForDisplayMove()); + + // Move the app to a secondary display, and the restart menu must get enabled. + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.copyFrom(mDisplayInfo); + displayInfo.displayId = DEFAULT_DISPLAY + 1; + final DisplayContent secondaryDisplay = createNewDisplay(displayInfo); + task.reparent(secondaryDisplay.getDefaultTaskDisplayArea(), true); + assertTrue(task.getTaskInfo().appCompatTaskInfo.isRestartMenuEnabledForDisplayMove()); + + // Once the app gets restarted, the restart menu must be gone. + activity.restartProcessIfVisible(); + assertFalse(task.getTaskInfo().appCompatTaskInfo.isRestartMenuEnabledForDisplayMove()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index 71e34ef220d3..3c6a89842af9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -93,7 +93,7 @@ public class InsetsPolicyTest extends WindowTestsBase { final Task task1 = createTask(mDisplayContent); final Task task2 = createTask(mDisplayContent); - task1.setAdjacentTaskFragment(task2); + task1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(task1, task2)); final WindowState win = createAppWindow(task1, WINDOWING_MODE_MULTI_WINDOW, "app"); final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win); 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 33a48aadbd70..e2c4a1d2dfea 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -572,8 +572,8 @@ public class SizeCompatTests extends WindowTestsBase { new TestDisplayContent.Builder(mAtm, 1000, 2000).build(); final InputDevice device = new InputDevice.Builder() .setAssociatedDisplayId(newDisplay.mDisplayId) - .setSources(InputDevice.SOURCE_TOUCHSCREEN | InputDevice.SOURCE_TRACKBALL - | InputDevice.KEYBOARD_TYPE_ALPHABETIC) + .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC) + .setSources(InputDevice.SOURCE_TOUCHSCREEN | InputDevice.SOURCE_TRACKBALL) .build(); final InputDevice[] devices = {device}; doReturn(true).when(newDisplay.mWmService.mInputManager) @@ -596,6 +596,7 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(originalTouchscreen, newConfiguration.touchscreen); assertEquals(originalNavigation, newConfiguration.navigation); assertEquals(originalKeyboard, newConfiguration.keyboard); + // TODO(b/399749909): assert keyboardHidden, hardkeyboardHidden, and navigationHidden too. } @Test 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 3776b03695d5..b558fad84efa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -78,6 +78,7 @@ import android.view.SurfaceControl; import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.internal.os.BackgroundThread; +import com.android.internal.protolog.PerfettoProtoLogImpl; import com.android.internal.protolog.ProtoLog; import com.android.internal.protolog.WmProtoLogGroups; import com.android.server.AnimationThread; @@ -187,7 +188,10 @@ public class SystemServicesTestRule implements TestRule { } private void setUp() { - ProtoLog.init(WmProtoLogGroups.values()); + if (ProtoLog.getSingleInstance() == null) { + ProtoLog.init(WmProtoLogGroups.values()); + PerfettoProtoLogImpl.waitForInitialization(); + } if (mOnBeforeServicesCreated != null) { mOnBeforeServicesCreated.run(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java index 986532ce5897..ec83c50e95aa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java @@ -87,7 +87,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase { mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); adjacentRootTask.mCreatedByOrganizer = true; final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); - adjacentRootTask.setAdjacentTaskFragment(rootTask); + adjacentRootTask.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(adjacentRootTask, rootTask)); taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask); Task actualRootTask = taskDisplayArea.getLaunchRootTask( @@ -113,7 +114,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase { final Task adjacentRootTask = createTask( mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); adjacentRootTask.mCreatedByOrganizer = true; - adjacentRootTask.setAdjacentTaskFragment(rootTask); + adjacentRootTask.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(adjacentRootTask, rootTask)); taskDisplayArea.setLaunchRootTask(rootTask, new int[]{WINDOWING_MODE_MULTI_WINDOW}, new int[]{ACTIVITY_TYPE_STANDARD}); @@ -135,7 +137,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase { adjacentRootTask.mCreatedByOrganizer = true; createActivityRecord(adjacentRootTask); final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); - adjacentRootTask.setAdjacentTaskFragment(rootTask); + adjacentRootTask.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(adjacentRootTask, rootTask)); taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask); final Task actualRootTask = taskDisplayArea.getLaunchRootTask( @@ -821,7 +824,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase { adjacentRootTask.mCreatedByOrganizer = true; final Task candidateTask = createTaskInRootTask(rootTask, 0 /* userId*/); final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); - adjacentRootTask.setAdjacentTaskFragment(rootTask); + adjacentRootTask.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(adjacentRootTask, rootTask)); // Verify the launch root with candidate task Task actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED, diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index ab76ae8e378a..76660bdc7355 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -784,7 +784,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { .setFragmentToken(fragmentToken2) .build(); mWindowOrganizerController.mLaunchTaskFragments.put(fragmentToken2, taskFragment2); - mTaskFragment.setAdjacentTaskFragment(taskFragment2); + mTaskFragment.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(mTaskFragment, taskFragment2)); mTransaction.clearAdjacentTaskFragments(mFragmentToken); mOrganizer.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE, @@ -1267,7 +1268,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test - public void testTaskFragmentInPip_setAdjacentTaskFragment() { + public void testTaskFragmentInPip_setAdjacentTaskFragments() { setupTaskFragmentInPip(); spyOn(mWindowOrganizerController); @@ -1279,7 +1280,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), eq(mErrorToken), eq(mTaskFragment), eq(OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS), any(IllegalArgumentException.class)); - verify(mTaskFragment, never()).setAdjacentTaskFragment(any()); + verify(mTaskFragment, never()).setAdjacentTaskFragments(any()); } @Test 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 cc2a76dcc9f2..7c1d7fec819b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -67,7 +67,6 @@ import android.content.res.Configuration; import android.graphics.Color; import android.graphics.Rect; import android.os.Binder; -import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.SurfaceControl; import android.view.View; @@ -363,7 +362,7 @@ public class TaskFragmentTest extends WindowTestsBase { doReturn(true).when(primaryActivity).supportsPictureInPicture(); doReturn(false).when(secondaryActivity).supportsPictureInPicture(); - primaryTf.setAdjacentTaskFragment(secondaryTf); + primaryTf.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(primaryTf, secondaryTf)); primaryActivity.setState(RESUMED, "test"); secondaryActivity.setState(RESUMED, "test"); @@ -390,7 +389,8 @@ public class TaskFragmentTest extends WindowTestsBase { task.setWindowingMode(WINDOWING_MODE_FULLSCREEN); taskFragment0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); taskFragment0.setBounds(taskFragmentBounds); - taskFragment0.setAdjacentTaskFragment(taskFragment1); + taskFragment0.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(taskFragment0, taskFragment1)); taskFragment0.setCompanionTaskFragment(taskFragment1); taskFragment0.setAnimationParams(new TaskFragmentAnimationParams.Builder() .setAnimationBackgroundColor(Color.GREEN) @@ -779,7 +779,7 @@ public class TaskFragmentTest extends WindowTestsBase { .setOrganizer(mOrganizer) .setFragmentToken(new Binder()) .build(); - tf0.setAdjacentTaskFragment(tf1); + tf0.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf0, tf1)); tf0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); tf1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); task.setBounds(0, 0, 1200, 1000); @@ -834,7 +834,7 @@ public class TaskFragmentTest extends WindowTestsBase { final Task task = createTask(mDisplayContent); final TaskFragment tf0 = createTaskFragmentWithActivity(task); final TaskFragment tf1 = createTaskFragmentWithActivity(task); - tf0.setAdjacentTaskFragment(tf1); + tf0.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf0, tf1)); tf0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); tf1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); task.setBounds(0, 0, 1200, 1000); @@ -982,7 +982,8 @@ public class TaskFragmentTest extends WindowTestsBase { .setOrganizer(mOrganizer) .setFragmentToken(new Binder()) .build(); - taskFragmentLeft.setAdjacentTaskFragment(taskFragmentRight); + taskFragmentLeft.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(taskFragmentLeft, taskFragmentRight)); taskFragmentLeft.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); taskFragmentRight.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); task.setBounds(0, 0, 1200, 1000); @@ -1051,8 +1052,8 @@ public class TaskFragmentTest extends WindowTestsBase { .setParentTask(task) .createActivityCount(1) .build(); - taskFragmentRight.setAdjacentTaskFragment(taskFragmentLeft); - taskFragmentLeft.setAdjacentTaskFragment(taskFragmentRight); + taskFragmentRight.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(taskFragmentLeft, taskFragmentRight)); final ActivityRecord appLeftTop = taskFragmentLeft.getTopMostActivity(); final ActivityRecord appRightTop = taskFragmentRight.getTopMostActivity(); @@ -1103,7 +1104,6 @@ public class TaskFragmentTest extends WindowTestsBase { Math.min(outConfig.screenWidthDp, outConfig.screenHeightDp)); } - @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) @Test public void testAdjacentSetForTaskFragments() { final Task task = createTask(mDisplayContent); @@ -1119,7 +1119,6 @@ public class TaskFragmentTest extends WindowTestsBase { () -> new TaskFragment.AdjacentSet(tf0, tf1, tf2)); } - @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) @Test public void testSetAdjacentTaskFragments() { final Task task0 = createTask(mDisplayContent); @@ -1148,7 +1147,6 @@ public class TaskFragmentTest extends WindowTestsBase { assertFalse(task2.hasAdjacentTaskFragment()); } - @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) @Test public void testClearAdjacentTaskFragments() { final Task task0 = createTask(mDisplayContent); @@ -1167,7 +1165,6 @@ public class TaskFragmentTest extends WindowTestsBase { assertFalse(task2.hasAdjacentTaskFragment()); } - @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) @Test public void testRemoveFromAdjacentTaskFragments() { final Task task0 = createTask(mDisplayContent); @@ -1190,7 +1187,6 @@ public class TaskFragmentTest extends WindowTestsBase { assertFalse(task1.isAdjacentTo(task1)); } - @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) @Test public void testRemoveFromAdjacentTaskFragmentsWhenRemove() { final Task task0 = createTask(mDisplayContent); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 724d7e7c111c..044aacc4b988 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -1750,8 +1750,7 @@ public class TaskTests extends WindowTestsBase { primary.mVisibleRequested = true; secondary.mVisibleRequested = true; - primary.setAdjacentTaskFragment(secondary); - secondary.setAdjacentTaskFragment(primary); + primary.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(primary, secondary)); primary.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK); doReturn(true).when(primary).shouldBoostDimmer(); task.assignChildLayers(t); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 7030d986494f..5401a44d7016 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -910,7 +910,7 @@ public class WindowOrganizerTests extends WindowTestsBase { final RunningTaskInfo info2 = task2.getTaskInfo(); WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setAdjacentRoots(info1.token, info2.token); + wct.setAdjacentRootSet(info1.token, info2.token); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertTrue(task1.isAdjacentTo(task2)); assertTrue(task2.isAdjacentTo(task1)); @@ -929,7 +929,6 @@ public class WindowOrganizerTests extends WindowTestsBase { assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, null); } - @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) @Test public void testSetAdjacentLaunchRootSet() { final DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY); 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 57ab13ffee89..471b065aebb4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -122,7 +122,6 @@ import android.window.WindowContainerTransaction; import com.android.internal.policy.AttributeCache; import com.android.internal.util.ArrayUtils; import com.android.internal.util.test.FakeSettingsProvider; -import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils; import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry; import org.junit.After; @@ -289,18 +288,6 @@ public class WindowTestsBase extends SystemServiceTestsBase { mAtm.mWindowManager.mAppCompatConfiguration .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(false); - // Setup WallpaperController crop utils with a simple center-align strategy - WallpaperCropUtils cropUtils = (displaySize, bitmapSize, suggestedCrops, rtl) -> { - Rect crop = new Rect(0, 0, displaySize.x, displaySize.y); - crop.scale(Math.min( - ((float) bitmapSize.x) / displaySize.x, - ((float) bitmapSize.y) / displaySize.y)); - crop.offset((bitmapSize.x - crop.width()) / 2, (bitmapSize.y - crop.height()) / 2); - return crop; - }; - mDisplayContent.mWallpaperController.setWallpaperCropUtils(cropUtils); - mDefaultDisplay.mWallpaperController.setWallpaperCropUtils(cropUtils); - checkDeviceSpecificOverridesNotApplied(); } @@ -1890,7 +1877,7 @@ public class WindowTestsBase extends SystemServiceTestsBase { mSecondary = mService.mTaskOrganizerController.createRootTask( display, WINDOWING_MODE_MULTI_WINDOW, null); - mPrimary.setAdjacentTaskFragment(mSecondary); + mPrimary.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(mPrimary, mSecondary)); display.getDefaultTaskDisplayArea().setLaunchAdjacentFlagRootTask(mSecondary); final Rect primaryBounds = new Rect(); |