summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt1
-rw-r--r--core/java/android/app/Notification.java10
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java5
-rw-r--r--core/java/android/os/IPowerManager.aidl2
-rw-r--r--core/java/android/os/PowerManager.java10
-rw-r--r--core/java/android/provider/Settings.java12
-rw-r--r--core/java/android/view/SurfaceControl.java25
-rw-r--r--core/java/android/view/ViewRootImpl.java2
-rw-r--r--core/java/android/window/OnBackInvokedDispatcher.java18
-rw-r--r--core/java/android/window/ProxyOnBackInvokedDispatcher.java19
-rw-r--r--core/java/android/window/TaskConstants.java12
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java137
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig8
-rw-r--r--core/jni/android_view_SurfaceControl.cpp12
-rw-r--r--core/res/AndroidManifest.xml1
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java140
-rw-r--r--libs/WindowManager/Shell/res/color/open_by_default_settings_dialog_radio_button_color.xml22
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_by_default_settings.xml25
-rw-r--r--libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_background.xml (renamed from packages/SystemUI/res/layout/notification_template_en_route_contracted.xml)17
-rw-r--r--libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_dismiss_button_background.xml22
-rw-r--r--libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_radio_buttons_background.xml21
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml13
-rw-r--r--libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml130
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml8
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt155
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt)10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt)12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt2
-rw-r--r--packages/SettingsLib/res/values/strings.xml3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java6
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java16
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java38
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt85
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt108
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt2
-rw-r--r--packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml100
-rw-r--r--packages/SystemUI/res-keyguard/layout-land/keyguard_sim_pin_view.xml221
-rw-r--r--packages/SystemUI/res-keyguard/layout-land/keyguard_sim_puk_view.xml223
-rw-r--r--packages/SystemUI/res/layout/notification_template_en_route_expanded.xml86
-rw-r--r--packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml122
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt212
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt218
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt226
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepository.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/IconModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt103
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingNotificationFlag.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/ConfigurationTracker.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt96
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt97
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt120
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt545
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepositoryKosmos.kt28
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt28
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelKosmos.kt28
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java19
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java6
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java5
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java98
-rw-r--r--services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java16
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java25
-rw-r--r--services/core/java/com/android/server/vibrator/ExternalVibrationSession.java46
-rw-r--r--services/core/java/com/android/server/vibrator/HalVibration.java81
-rw-r--r--services/core/java/com/android/server/vibrator/SingleVibrationSession.java173
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java49
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSession.java41
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStats.java42
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStepConductor.java41
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java424
-rw-r--r--services/core/java/com/android/server/wm/TrustedPresentationListenerController.java40
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java61
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java3
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java113
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java44
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java22
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java2
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java43
-rw-r--r--telephony/common/com/android/internal/telephony/CarrierAppUtils.java22
-rw-r--r--tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java9
-rw-r--r--tests/Internal/src/com/android/internal/os/OWNERS2
139 files changed, 2740 insertions, 3437 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index b740ef36debe..53da33835d31 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -61658,6 +61658,7 @@ package android.window {
method public void unregisterOnBackInvokedCallback(@NonNull android.window.OnBackInvokedCallback);
field public static final int PRIORITY_DEFAULT = 0; // 0x0
field public static final int PRIORITY_OVERLAY = 1000000; // 0xf4240
+ field @FlaggedApi("com.android.window.flags.predictive_back_priority_system_navigation_observer") public static final int PRIORITY_SYSTEM_NAVIGATION_OBSERVER = -2; // 0xfffffffe
}
public interface SplashScreen {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0812a1345e44..38632bdeeff5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -809,6 +809,10 @@ public class Notification implements Parcelable
return false;
}
+ private static boolean isStandardLayout(int layoutId) {
+ return STANDARD_LAYOUTS.contains(layoutId);
+ }
+
/** @hide */
@IntDef(flag = true, prefix = {"FLAG_"}, value = {
FLAG_SHOW_LIGHTS,
@@ -5983,9 +5987,9 @@ public class Notification implements Parcelable
}
}
boolean contentViewUsesHeader = mN.contentView == null
- || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId());
+ || isStandardLayout(mN.contentView.getLayoutId());
boolean bigContentViewUsesHeader = mN.bigContentView == null
- || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId());
+ || isStandardLayout(mN.bigContentView.getLayoutId());
return contentViewUsesHeader && bigContentViewUsesHeader;
}
@@ -6781,7 +6785,7 @@ public class Notification implements Parcelable
return false;
}
if (fullyCustomViewRequiresDecoration(false)
- && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) {
+ && isStandardLayout(customContent.getLayoutId())) {
// If the app's custom views are objects returned from Builder.create*ContentView()
// then the app is most likely attempting to spoof the user. Even if they are not,
// the result would be broken (b/189189308) so we will ignore it.
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 36e816af8439..6c1aa90c831b 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -92,9 +92,10 @@ public abstract class DisplayManagerInternal {
boolean waitForNegativeProximity);
/**
- * Returns {@code true} if the proximity sensor screen-off function is available.
+ * Returns {@code true} if the proximity sensor screen-off function is available for the given
+ * display.
*/
- public abstract boolean isProximitySensorAvailable();
+ public abstract boolean isProximitySensorAvailable(int displayId);
/**
* Registers a display group listener which will be informed of the addition, removal, or change
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 5f62b8be45a3..e85e58039828 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -42,7 +42,9 @@ interface IPowerManager
void updateWakeLockWorkSource(IBinder lock, in WorkSource ws, String historyTag);
void updateWakeLockCallback(IBinder lock, IWakeLockCallback callback);
+ @UnsupportedAppUsage
boolean isWakeLockLevelSupported(int level);
+ boolean isWakeLockLevelSupportedWithDisplayId(int level, int displayId);
void userActivity(int displayId, long time, int event, int flags);
void wakeUp(long time, int reason, String details, String opPackageName);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index b9bae5b9f737..32db3bea7686 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1344,6 +1344,9 @@ public final class PowerManager {
* @see #ON_AFTER_RELEASE
*/
public WakeLock newWakeLock(int levelAndFlags, String tag) {
+ if (android.companion.virtualdevice.flags.Flags.displayPowerManagerApis()) {
+ return newWakeLock(levelAndFlags, tag, mContext.getDisplayId());
+ }
validateWakeLockParameters(levelAndFlags, tag);
return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName(),
Display.INVALID_DISPLAY);
@@ -1734,6 +1737,10 @@ public final class PowerManager {
*/
public boolean isWakeLockLevelSupported(int level) {
try {
+ if (android.companion.virtualdevice.flags.Flags.displayPowerManagerApis()) {
+ return mService.isWakeLockLevelSupportedWithDisplayId(
+ level, mContext.getDisplayId());
+ }
return mService.isWakeLockLevelSupported(level);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1797,6 +1804,9 @@ public final class PowerManager {
* @see android.content.Intent#ACTION_SCREEN_OFF
*/
public boolean isInteractive() {
+ if (android.companion.virtualdevice.flags.Flags.displayPowerManagerApis()) {
+ return isInteractive(mContext.getDisplayId());
+ }
return mInteractiveCache.query(null);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1e6f025fdbbe..1a15d09c0a50 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -17813,12 +17813,6 @@ public final class Settings {
public static final String FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT =
"force_non_debuggable_final_build_for_compat";
- /**
- * Flag to enable the use of ApplicationInfo for getting not-launched status.
- *
- * @hide
- */
- public static final String ENABLE_USE_APP_INFO_NOT_LAUNCHED = "use_app_info_not_launched";
/**
* Current version of signed configuration applied.
@@ -20164,6 +20158,12 @@ public final class Settings {
public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_SUCCESS = 11;
/**
+ * Phone switching has finished account match step.
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_STATUS_ACCOUNTS_MATCHED = 12;
+
+ /**
* Phone switching request source
* @hide
*/
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 90ceb440ea5e..2748e3288d34 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -152,6 +152,8 @@ public final class SurfaceControl implements Parcelable {
long nativeObject, int priority);
private static native void nativeSetWindowCrop(long transactionObj, long nativeObject,
int l, int t, int r, int b);
+ private static native void nativeSetCrop(long transactionObj, long nativeObject,
+ float l, float t, float r, float b);
private static native void nativeSetCornerRadius(long transactionObj, long nativeObject,
float cornerRadius);
private static native void nativeSetBackgroundBlurRadius(long transactionObj, long nativeObject,
@@ -3452,6 +3454,29 @@ public final class SurfaceControl implements Parcelable {
}
/**
+ * Bounds the surface and its children to the bounds specified. Size of the surface will be
+ * ignored and only the crop and buffer size will be used to determine the bounds of the
+ * surface. If no crop is specified and the surface has no buffer, the surface bounds is
+ * only constrained by the size of its parent bounds.
+ *
+ * @param sc SurfaceControl to set crop of.
+ * @param crop Bounds of the crop to apply.
+ * @return this This transaction for chaining
+ * @hide
+ */
+ public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, float top, float left,
+ float bottom, float right) {
+ checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setCrop", this, sc, "crop={" + top + ", " + left + ", " +
+ bottom + ", " + right + "}");
+ }
+ nativeSetCrop(mNativeObject, sc.mNativeObject, top, left, bottom, right);
+ return this;
+ }
+
+ /**
* Sets the corner radius of a {@link SurfaceControl}.
* @param sc SurfaceControl
* @param cornerRadius Corner radius in pixels.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8fb17c72c462..0ca442d66e6f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -7531,6 +7531,7 @@ public final class ViewRootImpl implements ViewParent,
if (keyEvent.isCanceled()) {
animationCallback.onBackCancelled();
} else {
+ dispatcher.tryInvokeSystemNavigationObserverCallback();
topCallback.onBackInvoked();
}
break;
@@ -7538,6 +7539,7 @@ public final class ViewRootImpl implements ViewParent,
} else if (topCallback != null) {
if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
if (!keyEvent.isCanceled()) {
+ dispatcher.tryInvokeSystemNavigationObserverCallback();
topCallback.onBackInvoked();
} else {
Log.d(mTag, "Skip onBackInvoked(), reason: keyEvent.isCanceled=true");
diff --git a/core/java/android/window/OnBackInvokedDispatcher.java b/core/java/android/window/OnBackInvokedDispatcher.java
index 0632a37a5dfb..02ed57d4be22 100644
--- a/core/java/android/window/OnBackInvokedDispatcher.java
+++ b/core/java/android/window/OnBackInvokedDispatcher.java
@@ -16,11 +16,14 @@
package android.window;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
+import com.android.window.flags.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -43,6 +46,7 @@ public interface OnBackInvokedDispatcher {
@IntDef({
PRIORITY_DEFAULT,
PRIORITY_OVERLAY,
+ PRIORITY_SYSTEM_NAVIGATION_OBSERVER,
})
@Retention(RetentionPolicy.SOURCE)
@interface Priority{}
@@ -67,6 +71,20 @@ public interface OnBackInvokedDispatcher {
int PRIORITY_SYSTEM = -1;
/**
+ * Priority level of {@link OnBackInvokedCallback}s designed to observe system-level back
+ * handling.
+ *
+ * <p>Callbacks registered with this priority do not consume back events. They receive back
+ * events whenever the system handles a back navigation and have no impact on the normal back
+ * navigation flow. Useful for logging or analytics.
+ *
+ * <p>Only one callback with {@link #PRIORITY_SYSTEM_NAVIGATION_OBSERVER} can be registered at a
+ * time.
+ */
+ @FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+ int PRIORITY_SYSTEM_NAVIGATION_OBSERVER = -2;
+
+ /**
* Registers a {@link OnBackInvokedCallback}.
*
* Within the same priority level, callbacks are invoked in the reverse order in which
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index 56c05b2766ec..dfc4a589f92e 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -16,6 +16,8 @@
package android.window;
+import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -95,7 +97,7 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
synchronized (mLock) {
mCallbacks.add(Pair.create(callback, priority));
if (mActualDispatcher != null) {
- if (priority <= PRIORITY_SYSTEM) {
+ if (priority == PRIORITY_SYSTEM) {
mActualDispatcher.registerSystemOnBackInvokedCallback(callback);
} else {
mActualDispatcher.registerOnBackInvokedCallback(priority, callback);
@@ -123,10 +125,19 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) {
int priority = callbackPair.second;
- if (priority >= PRIORITY_DEFAULT) {
- mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
+ if (predictiveBackPrioritySystemNavigationObserver()) {
+ if (priority >= PRIORITY_DEFAULT
+ || priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
+ mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
+ } else {
+ mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
+ }
} else {
- mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
+ if (priority >= PRIORITY_DEFAULT) {
+ mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
+ } else {
+ mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
+ }
}
}
mCallbacks.clear();
diff --git a/core/java/android/window/TaskConstants.java b/core/java/android/window/TaskConstants.java
index 44bb33db6347..46bc30e2bb2d 100644
--- a/core/java/android/window/TaskConstants.java
+++ b/core/java/android/window/TaskConstants.java
@@ -53,25 +53,30 @@ public class TaskConstants {
*/
public static final int TASK_CHILD_LAYER_COMPAT_UI = TASK_CHILD_LAYER_REGION_SIZE;
+ /**
+ * Settings dialogs belonging to the task (e.g. Open by default settings dialog)
+ * @hide
+ */
+ public static final int TASK_CHILD_LAYER_SETTINGS_DIALOG = 2 * TASK_CHILD_LAYER_REGION_SIZE;
/**
* Captions, window frames and resize handlers around task windows.
* @hide
*/
- public static final int TASK_CHILD_LAYER_WINDOW_DECORATIONS = 2 * TASK_CHILD_LAYER_REGION_SIZE;
+ public static final int TASK_CHILD_LAYER_WINDOW_DECORATIONS = 3 * TASK_CHILD_LAYER_REGION_SIZE;
/**
* Overlays the task when going into PIP w/ gesture navigation.
* @hide
*/
public static final int TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY =
- 3 * TASK_CHILD_LAYER_REGION_SIZE;
+ 4 * TASK_CHILD_LAYER_REGION_SIZE;
/**
* Allows other apps to add overlays on the task (i.e. game dashboard)
* @hide
*/
- public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 4 * TASK_CHILD_LAYER_REGION_SIZE;
+ public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 5 * TASK_CHILD_LAYER_REGION_SIZE;
/**
@@ -95,6 +100,7 @@ public class TaskConstants {
TASK_CHILD_LAYER_TASK_BACKGROUND,
TASK_CHILD_LAYER_LETTERBOX_BACKGROUND,
TASK_CHILD_LAYER_COMPAT_UI,
+ TASK_CHILD_LAYER_SETTINGS_DIALOG,
TASK_CHILD_LAYER_WINDOW_DECORATIONS,
TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY,
TASK_CHILD_LAYER_TASK_OVERLAY,
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index bb89a2499838..51bc7d5c571b 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -16,6 +16,8 @@
package android.window;
+import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
@@ -103,6 +105,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
public final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
mOnBackInvokedCallbacks = new TreeMap<>();
+ @VisibleForTesting
+ public OnBackInvokedCallback mSystemNavigationObserverCallback = null;
+
private Checker mChecker;
private final Object mLock = new Object();
// The threshold for back swipe full progress.
@@ -170,6 +175,20 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
}
+ private void registerSystemNavigationObserverCallback(@NonNull OnBackInvokedCallback callback) {
+ synchronized (mLock) {
+ // If callback has already been added as regular callback, remove it.
+ if (mAllCallbacks.containsKey(callback)) {
+ if (DEBUG) {
+ Log.i(TAG, "Callback already added. Removing and re-adding it as "
+ + "system-navigation-observer-callback.");
+ }
+ removeCallbackInternal(callback);
+ }
+ mSystemNavigationObserverCallback = callback;
+ }
+ }
+
/**
* Register a callback bypassing platform checks. This is used to register compatibility
* callbacks.
@@ -181,6 +200,12 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
return;
}
+ if (predictiveBackPrioritySystemNavigationObserver()) {
+ if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
+ registerSystemNavigationObserverCallback(callback);
+ return;
+ }
+ }
if (callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) {
if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback
&& mImeBackAnimationController != null) {
@@ -202,6 +227,13 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
Integer prevPriority = mAllCallbacks.get(callback);
mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
}
+ if (mSystemNavigationObserverCallback == callback) {
+ mSystemNavigationObserverCallback = null;
+ if (DEBUG) {
+ Log.i(TAG, "Callback already registered (as system-navigation-observer "
+ + "callback). Removing and re-adding it.");
+ }
+ }
OnBackInvokedCallback previousTopCallback = getTopCallback();
callbacks.add(callback);
@@ -221,6 +253,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
mImeDispatcher.unregisterOnBackInvokedCallback(callback);
return;
}
+ if (mSystemNavigationObserverCallback == callback) {
+ mSystemNavigationObserverCallback = null;
+ return;
+ }
if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
callback = mImeBackAnimationController;
}
@@ -230,25 +266,29 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
return;
}
- OnBackInvokedCallback previousTopCallback = getTopCallback();
- Integer priority = mAllCallbacks.get(callback);
- ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
- callbacks.remove(callback);
- if (callbacks.isEmpty()) {
- mOnBackInvokedCallbacks.remove(priority);
- }
- mAllCallbacks.remove(callback);
- // Re-populate the top callback to WM if the removed callback was previously the top
- // one.
- if (previousTopCallback == callback) {
- // We should call onBackCancelled() when an active callback is removed from
- // dispatcher.
- mProgressAnimator.removeOnBackCancelledFinishCallback();
- mProgressAnimator.removeOnBackInvokedFinishCallback();
- sendCancelledIfInProgress(callback);
- mHandler.post(mProgressAnimator::reset);
- setTopOnBackInvokedCallback(getTopCallback());
- }
+ removeCallbackInternal(callback);
+ }
+ }
+
+ private void removeCallbackInternal(@NonNull OnBackInvokedCallback callback) {
+ OnBackInvokedCallback previousTopCallback = getTopCallback();
+ Integer priority = mAllCallbacks.get(callback);
+ ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+ callbacks.remove(callback);
+ if (callbacks.isEmpty()) {
+ mOnBackInvokedCallbacks.remove(priority);
+ }
+ mAllCallbacks.remove(callback);
+ // Re-populate the top callback to WM if the removed callback was previously the top
+ // one.
+ if (previousTopCallback == callback) {
+ // We should call onBackCancelled() when an active callback is removed from
+ // dispatcher.
+ mProgressAnimator.removeOnBackCancelledFinishCallback();
+ mProgressAnimator.removeOnBackInvokedFinishCallback();
+ sendCancelledIfInProgress(callback);
+ mHandler.post(mProgressAnimator::reset);
+ setTopOnBackInvokedCallback(getTopCallback());
}
}
@@ -304,6 +344,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
mHandler.post(mProgressAnimator::reset);
mAllCallbacks.clear();
mOnBackInvokedCallbacks.clear();
+ mSystemNavigationObserverCallback = null;
}
}
@@ -315,6 +356,25 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
}
+ /**
+ * Tries to call {@link OnBackInvokedCallback#onBackInvoked} on the system navigation observer
+ * callback (if one is set and if the top-most regular callback has
+ * {@link OnBackInvokedDispatcher#PRIORITY_SYSTEM})
+ */
+ public void tryInvokeSystemNavigationObserverCallback() {
+ OnBackInvokedCallback topCallback = getTopCallback();
+ Integer callbackPriority = mAllCallbacks.getOrDefault(topCallback, null);
+ if (callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) {
+ invokeSystemNavigationObserverCallback();
+ }
+ }
+
+ private void invokeSystemNavigationObserverCallback() {
+ if (mSystemNavigationObserverCallback != null) {
+ mSystemNavigationObserverCallback.onBackInvoked();
+ }
+ }
+
private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
if (mWindowSession == null || mWindow == null) {
return;
@@ -324,7 +384,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
if (callback != null) {
int priority = mAllCallbacks.get(callback);
final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback,
- mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme);
+ mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme,
+ this::invokeSystemNavigationObserverCallback,
+ /*isSystemCallback*/ priority == PRIORITY_SYSTEM);
callbackInfo = new OnBackInvokedCallbackInfo(
iCallback,
priority,
@@ -416,18 +478,26 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
private final Handler mHandler;
@NonNull
private final BooleanSupplier mOnKeyPreIme;
+ @NonNull
+ private final Runnable mSystemNavigationObserverCallbackRunnable;
+ private final boolean mIsSystemCallback;
OnBackInvokedCallbackWrapper(
@NonNull OnBackInvokedCallback callback,
@NonNull BackTouchTracker touchTracker,
@NonNull BackProgressAnimator progressAnimator,
@NonNull Handler handler,
- @NonNull BooleanSupplier onKeyPreIme) {
+ @NonNull BooleanSupplier onKeyPreIme,
+ @NonNull Runnable systemNavigationObserverCallbackRunnable,
+ boolean isSystemCallback
+ ) {
mCallback = new WeakReference<>(callback);
mTouchTracker = touchTracker;
mProgressAnimator = progressAnimator;
mHandler = handler;
mOnKeyPreIme = onKeyPreIme;
+ mSystemNavigationObserverCallbackRunnable = systemNavigationObserverCallbackRunnable;
+ mIsSystemCallback = isSystemCallback;
}
@Override
@@ -494,9 +564,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
OnBackAnimationCallback animationCallback = getBackAnimationCallback();
if (animationCallback != null
&& !(callback instanceof ImeBackAnimationController)) {
- mProgressAnimator.onBackInvoked(callback::onBackInvoked);
+ mProgressAnimator.onBackInvoked(() -> {
+ if (mIsSystemCallback) {
+ mSystemNavigationObserverCallbackRunnable.run();
+ }
+ callback.onBackInvoked();
+ });
} else {
mProgressAnimator.reset();
+ if (mIsSystemCallback) {
+ mSystemNavigationObserverCallbackRunnable.run();
+ }
callback.onBackInvoked();
}
});
@@ -597,9 +675,18 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
+ " application manifest.");
return false;
}
- if (priority < 0) {
- throw new IllegalArgumentException("Application registered OnBackInvokedCallback "
- + "cannot have negative priority. Priority: " + priority);
+ if (predictiveBackPrioritySystemNavigationObserver()) {
+ if (priority < 0 && priority != PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
+ throw new IllegalArgumentException("Application registered "
+ + "OnBackInvokedCallback cannot have negative priority. Priority: "
+ + priority);
+ }
+ } else {
+ if (priority < 0) {
+ throw new IllegalArgumentException("Application registered "
+ + "OnBackInvokedCallback cannot have negative priority. Priority: "
+ + priority);
+ }
}
Objects.requireNonNull(callback);
return true;
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 086063f3887c..c9b93c95e0c1 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -307,3 +307,11 @@ flag {
bug: "364930619"
is_fixed_read_only: true
}
+
+flag {
+ name: "predictive_back_priority_system_navigation_observer"
+ namespace: "systemui"
+ description: "PRIORITY_SYSTEM_NAVIGATION_OBSERVER predictive back API extension"
+ is_fixed_read_only: true
+ bug: "362938401"
+}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 17c89f88b441..71ba2147d984 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -48,6 +48,7 @@
#include <ui/DisplayMode.h>
#include <ui/DisplayedFrameStats.h>
#include <ui/DynamicDisplayInfo.h>
+#include <ui/FloatRect.h>
#include <ui/FrameStats.h>
#include <ui/GraphicTypes.h>
#include <ui/HdrCapabilities.h>
@@ -992,6 +993,15 @@ static void nativeSetWindowCrop(JNIEnv* env, jclass clazz, jlong transactionObj,
transaction->setCrop(ctrl, crop);
}
+static void nativeSetCrop(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+ jfloat l, jfloat t, jfloat r, jfloat b) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ FloatRect crop(l, t, r, b);
+ transaction->setCrop(ctrl, crop);
+}
+
static void nativeSetCornerRadius(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jfloat cornerRadius) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2347,6 +2357,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeSetFrameRateSelectionPriority },
{"nativeSetWindowCrop", "(JJIIII)V",
(void*)nativeSetWindowCrop },
+ {"nativeSetCrop", "(JJFFFF)V",
+ (void*)nativeSetCrop },
{"nativeSetCornerRadius", "(JJF)V",
(void*)nativeSetCornerRadius },
{"nativeSetBackgroundBlurRadius", "(JJI)V",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ed33edecb333..f067b5184bdb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -792,6 +792,7 @@
<protected-broadcast android:name="com.android.cellbroadcastreceiver.GET_LATEST_CB_AREA_INFO" />
<protected-broadcast android:name="com.android.internal.telephony.ACTION_CARRIER_CERTIFICATE_DOWNLOAD" />
<protected-broadcast android:name="com.android.internal.telephony.action.COUNTRY_OVERRIDE" />
+ <protected-broadcast android:name="com.android.internal.telephony.action.SILENCE_WIFI_CALLING_NOTIFICATION"/>
<protected-broadcast android:name="com.android.internal.telephony.OPEN_DEFAULT_SMS_APP" />
<protected-broadcast android:name="com.android.internal.telephony.ACTION_TEST_OVERRIDE_CARRIER_ID" />
<protected-broadcast android:name="android.telephony.action.SIM_CARD_STATE_CHANGED" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1a3a30d9c36d..b90ee2b7e57f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1529,6 +1529,11 @@
factory reset. -->
<bool name="config_enableCredentialFactoryResetProtection">true</bool>
+ <!-- If true, then work around broken Weaver HALs that don't work reliably before the device has
+ fully booted. Setting this to true weakens a security feature; it should be done only when
+ necessary, though it is still better than not using Weaver at all. -->
+ <bool name="config_disableWeaverOnUnsecuredUsers">false</bool>
+
<!-- Control the behavior when the user long presses the home button.
0 - Nothing
1 - Launch all apps intent
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d5298acf0c27..c50c336f8fd8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3988,6 +3988,7 @@
<java-symbol type="string" name="foreground_service_multiple_separator" />
<java-symbol type="bool" name="config_enableCredentialFactoryResetProtection" />
+ <java-symbol type="bool" name="config_disableWeaverOnUnsecuredUsers" />
<!-- ETWS primary messages -->
<java-symbol type="string" name="etws_primary_default_message_earthquake" />
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 0bda0fff6819..0a4c5e6eb91a 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -18,6 +18,9 @@ package android.window;
import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY;
+import static android.window.OnBackInvokedDispatcher.PRIORITY_SYSTEM_NAVIGATION_OBSERVER;
+
+import static com.android.window.flags.Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -39,6 +42,10 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.view.IWindow;
import android.view.IWindowSession;
import android.view.ImeBackAnimationController;
@@ -80,6 +87,8 @@ public class WindowOnBackInvokedDispatcherTest {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock
private IWindowSession mWindowSession;
@@ -145,7 +154,8 @@ public class WindowOnBackInvokedDispatcherTest {
assertEquals("No setOnBackInvokedCallbackInfo", mCallbackInfoCalls, actual);
}
- private void assertCallbacksSize(int expectedDefault, int expectedOverlay) {
+ private void assertCallbacksSize(int expectedDefault, int expectedOverlay,
+ int expectedObserver) {
ArrayList<OnBackInvokedCallback> callbacksDefault = mDispatcher
.mOnBackInvokedCallbacks.get(PRIORITY_DEFAULT);
int actualSizeDefault = callbacksDefault != null ? callbacksDefault.size() : 0;
@@ -155,6 +165,10 @@ public class WindowOnBackInvokedDispatcherTest {
.mOnBackInvokedCallbacks.get(PRIORITY_OVERLAY);
int actualSizeOverlay = callbacksOverlay != null ? callbacksOverlay.size() : 0;
assertEquals("mOnBackInvokedCallbacks OVERLAY size", expectedOverlay, actualSizeOverlay);
+
+ int actualSizeObserver = mDispatcher.mSystemNavigationObserverCallback == null ? 0 : 1;
+ assertEquals("mOnBackInvokedCallbacks SYSTEM_NAVIGATION_OBSERVER size", expectedObserver,
+ actualSizeObserver);
}
private void assertTopCallback(OnBackInvokedCallback expectedCallback) {
@@ -164,13 +178,13 @@ public class WindowOnBackInvokedDispatcherTest {
@Test
public void registerCallback_samePriority_sameCallback() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
- assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback1);
// The callback is removed and added again
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
- assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback1);
@@ -182,13 +196,13 @@ public class WindowOnBackInvokedDispatcherTest {
@Test
public void registerCallback_samePriority_differentCallback() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
- assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback1);
// The new callback becomes the TopCallback
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
- assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 2, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback2);
@@ -201,13 +215,13 @@ public class WindowOnBackInvokedDispatcherTest {
@Test
public void registerCallback_differentPriority_sameCallback() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
- assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 1, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback1);
// The callback is moved to the new priority list
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
- assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback1);
@@ -220,13 +234,13 @@ public class WindowOnBackInvokedDispatcherTest {
public void registerCallback_differentPriority_differentCallback() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
assertSetCallbackInfo();
- assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 1, /* observer */ 0);
assertTopCallback(mCallback1);
// The callback with higher priority is still the TopCallback
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
assertNoSetCallbackInfo();
- assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 1, /* observer */ 0);
assertTopCallback(mCallback1);
waitForIdle();
@@ -238,22 +252,22 @@ public class WindowOnBackInvokedDispatcherTest {
@Test
public void registerCallback_sameInstanceAddedTwice() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
- assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 1, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback1);
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
- assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 1, /* observer */ 0);
assertNoSetCallbackInfo();
assertTopCallback(mCallback1);
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
- assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 2, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback1);
mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback2);
- assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 1, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mCallback2);
@@ -570,6 +584,102 @@ public class WindowOnBackInvokedDispatcherTest {
assertFalse(mDispatcher.mProgressAnimator.isBackAnimationInProgress());
}
+ @Test(expected = IllegalArgumentException.class)
+ @RequiresFlagsDisabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+ public void testNoUiCallback_registrationFailsWithoutFlaggedApiEnabled() {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+ public void testNoUiCallback_invokedWithSystemCallback() throws RemoteException {
+ mDispatcher.registerSystemOnBackInvokedCallback(mCallback1);
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+
+ assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1);
+ OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
+ assertTopCallback(mCallback1);
+
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+ waitForIdle();
+ verify(mCallback1).onBackStarted(any());
+ verify(mCallback2, never()).onBackStarted(any());
+
+ callbackInfo.getCallback().onBackProgressed(mBackEvent);
+ waitForIdle();
+ verify(mCallback1).onBackProgressed(any());
+ verify(mCallback2, never()).onBackProgressed(any());
+
+ callbackInfo.getCallback().onBackCancelled();
+ waitForIdle();
+ verify(mCallback1, timeout(1000)).onBackCancelled();
+ verify(mCallback2, never()).onBackCancelled();
+
+ // start new gesture to test onBackInvoked case
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+ callbackInfo.getCallback().onBackInvoked();
+ waitForIdle();
+ verify(mCallback1, timeout(1000)).onBackInvoked();
+ verify(mCallback2).onBackInvoked();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+ public void testNoUiCallback_notInvokedWithNonSystemCallback() throws RemoteException {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 1);
+ OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
+ assertTopCallback(mCallback1);
+
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+ waitForIdle();
+ verify(mCallback1).onBackStarted(any());
+ verify(mCallback2, never()).onBackStarted(any());
+
+ callbackInfo.getCallback().onBackProgressed(mBackEvent);
+ waitForIdle();
+ verify(mCallback1).onBackProgressed(any());
+ verify(mCallback2, never()).onBackProgressed(any());
+
+ callbackInfo.getCallback().onBackCancelled();
+ waitForIdle();
+ verify(mCallback1, timeout(1000)).onBackCancelled();
+ verify(mCallback2, never()).onBackCancelled();
+
+ // start new gesture to test onBackInvoked case
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+ callbackInfo.getCallback().onBackInvoked();
+ waitForIdle();
+ verify(mCallback1, timeout(1000)).onBackInvoked();
+ verify(mCallback2, never()).onBackInvoked();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+ public void testNoUiCallback_reregistrations() {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback1);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1);
+ assertEquals(mCallback1, mDispatcher.mSystemNavigationObserverCallback);
+
+ // test reregistration of observer-callback as observer-callback
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1);
+ assertEquals(mCallback2, mDispatcher.mSystemNavigationObserverCallback);
+
+ // test reregistration of observer-callback as regular callback
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
+
+ // test reregistration of regular callback as observer-callback
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1);
+
+ mDispatcher.unregisterOnBackInvokedCallback(mCallback2);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 0);
+ }
+
private BackMotionEvent backMotionEventFrom(float progress) {
return new BackMotionEvent(
/* touchX = */ 0,
@@ -585,13 +695,13 @@ public class WindowOnBackInvokedDispatcherTest {
private void verifyImeCallackRegistrations() throws RemoteException {
// verify default callback is replaced with ImeBackAnimationController
mDispatcher.registerOnBackInvokedCallbackUnchecked(mDefaultImeCallback, PRIORITY_DEFAULT);
- assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mImeBackAnimationController);
// verify regular ime callback is successfully registered
mDispatcher.registerOnBackInvokedCallbackUnchecked(mImeCallback, PRIORITY_DEFAULT);
- assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+ assertCallbacksSize(/* default */ 2, /* overlay */ 0, /* observer */ 0);
assertSetCallbackInfo();
assertTopCallback(mImeCallback);
}
diff --git a/libs/WindowManager/Shell/res/color/open_by_default_settings_dialog_radio_button_color.xml b/libs/WindowManager/Shell/res/color/open_by_default_settings_dialog_radio_button_color.xml
new file mode 100644
index 000000000000..0f9b28a07bde
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/open_by_default_settings_dialog_radio_button_color.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:state_checked="true"
+ android:color="?androidprv:attr/materialColorPrimaryContainer"/>
+ <item android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+</selector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_by_default_settings.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_by_default_settings.xml
new file mode 100644
index 000000000000..4070c3d577d8
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_by_default_settings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path android:fillColor="@android:color/black"
+ android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_background.xml
index e7a40d129d50..4eb22712f5e1 100644
--- a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml
+++ b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_background.xml
@@ -14,16 +14,9 @@
~ limitations under the License.
-->
-<com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
+<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@*android:id/status_bar_latest_event_content"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:minHeight="@*android:dimen/notification_headerless_min_height"
- android:tag="enroute"
- >
-
- <include layout="@*android:layout/notification_template_material_base" />
-
-</com.android.systemui.statusbar.notification.row.ui.view.EnRouteView> \ No newline at end of file
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+ <corners android:radius="28dp"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_dismiss_button_background.xml b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_dismiss_button_background.xml
new file mode 100644
index 000000000000..2b2e9df07dce
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_dismiss_button_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/materialColorPrimary"/>
+ <corners android:radius="50dp"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_radio_buttons_background.xml b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_radio_buttons_background.xml
new file mode 100644
index 000000000000..1ac952bf9112
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_radio_buttons_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ 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.
+ -->
+
+<shape android:shape="rectangle"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/open_by_default_settings_dialog_radio_button_color"/>
+ <corners android:radius="16dp"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 6913e54c2b10..aeb734e2d2d3 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -165,17 +165,28 @@
android:layout_height="@dimen/desktop_mode_handle_menu_open_in_browser_pill_height"
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
android:layout_marginStart="1dp"
- android:orientation="vertical"
+ android:orientation="horizontal"
android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:background="@drawable/desktop_mode_decor_handle_menu_background">
<Button
android:id="@+id/open_in_browser_button"
+ android:layout_weight="1"
android:contentDescription="@string/open_in_browser_text"
android:text="@string/open_in_browser_text"
android:drawableStart="@drawable/desktop_mode_ic_handle_menu_open_in_browser"
android:drawableTint="?androidprv:attr/materialColorOnSurface"
style="@style/DesktopModeHandleMenuActionButton"/>
+
+ <ImageButton
+ android:id="@+id/open_by_default_button"
+ android:layout_width="20dp"
+ android:layout_height="20dp"
+ android:layout_gravity="end|center_vertical"
+ android:layout_marginEnd="16dp"
+ android:contentDescription="@string/open_by_default_settings_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_open_by_default_settings"
+ android:tint="?androidprv:attr/materialColorOnSurface"/>
</LinearLayout>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml b/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml
new file mode 100644
index 000000000000..8ff382bbc7b4
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ 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.
+ -->
+
+<com.android.wm.shell.apptoweb.OpenByDefaultDialogView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ style="@style/LetterboxDialog">
+
+ <!-- The background of the top-level layout acts as the background dim. -->
+ <FrameLayout
+ android:id="@+id/open_by_default_dialog_container"
+ android:layout_width="@dimen/open_by_default_settings_dialog_width"
+ android:layout_height="wrap_content"
+ android:background="@drawable/open_by_default_settings_dialog_background"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <!-- The ScrollView should only wrap the content of the dialog, otherwise the background
+ corner radius will be cut off when scrolling to the top/bottom. -->
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:padding="24dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/application_icon"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_gravity="center_horizontal"
+ android:importantForAccessibility="no"
+ android:layout_marginTop="24dp"
+ android:layout_marginBottom="16dp"
+ android:scaleType="centerCrop"/>
+
+ <TextView
+ android:id="@+id/application_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="8dp"
+ android:lineHeight="32dp"
+ android:textFontWeight="400"
+ android:textSize="24sp"
+ android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+ tools:text="Gmail" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="12sp"
+ android:textFontWeight="400"
+ android:lineHeight="16dp"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="16dp"
+ android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+ android:text="@string/open_by_default_dialog_subheader_text"/>
+
+ <RadioGroup
+ android:id="@+id/open_by_default_radio_group"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal">
+ <RadioButton
+ android:id="@+id/open_in_app_button"
+ android:layout_width="@dimen/open_by_default_settings_dialog_radio_button_width"
+ android:layout_height="@dimen/open_by_default_settings_dialog_radio_button_height"
+ android:paddingStart="20dp"
+ android:paddingEnd="0dp"
+ android:layout_marginHorizontal="16dp"
+ android:layout_marginBottom="4dp"
+ android:text="@string/open_by_default_dialog_in_app_text"
+ android:textFontWeight="500"
+ android:textSize="16sp"
+ android:lineHeight="24dp"
+ android:background="@drawable/open_by_default_settings_dialog_radio_buttons_background"/>
+ <RadioButton
+ android:id="@+id/open_in_browser_button"
+ android:layout_width="@dimen/open_by_default_settings_dialog_radio_button_width"
+ android:layout_height="@dimen/open_by_default_settings_dialog_radio_button_height"
+ android:paddingStart="20dp"
+ android:paddingEnd="0dp"
+ android:layout_marginStart="16dp"
+ android:text="@string/open_by_default_dialog_in_browser_text"
+ android:textFontWeight="500"
+ android:textSize="16sp"
+ android:lineHeight="24dp"
+ android:background="@drawable/open_by_default_settings_dialog_radio_buttons_background"/>
+ </RadioGroup>
+
+ <Button
+ android:id="@+id/open_by_default_settings_dialog_dismiss_button"
+ android:layout_width="wrap_content"
+ android:layout_height="36dp"
+ android:text="@string/open_by_default_dialog_dismiss_button_text"
+ android:layout_gravity="end"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="32dp"
+ android:layout_marginBottom="24dp"
+ android:textSize="14sp"
+ android:textFontWeight="500"
+ android:textColor="?androidprv:attr/materialColorOnPrimary"
+ android:background="@drawable/open_by_default_settings_dialog_dismiss_button_background"/>
+ </LinearLayout>
+ </ScrollView>
+ </FrameLayout>
+</com.android.wm.shell.apptoweb.OpenByDefaultDialogView>
+
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index c7109f5be132..1f1565160965 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -624,4 +624,12 @@
<!-- The offset from the left edge of the entering page for the cross-activity animation -->
<dimen name="cross_activity_back_entering_start_offset">96dp</dimen>
+ <!-- The open by default settings dialog menu width. -->
+ <dimen name="open_by_default_settings_dialog_width">348dp</dimen>
+ <!-- The open by default settings dialog menu height. -->
+ <dimen name="open_by_default_settings_dialog_height">380dp</dimen>
+ <!-- The height of radio buttons in the open by default settings dialog. -->
+ <dimen name="open_by_default_settings_dialog_radio_button_height">64dp</dimen>
+ <!-- The width of radio buttons in the open by default settings dialog. -->
+ <dimen name="open_by_default_settings_dialog_radio_button_width">316dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 56f25dae3df2..5ef843210bbf 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -323,4 +323,15 @@
<string name="desktop_mode_maximize_menu_snap_left_button_text">Snap left</string>
<!-- Accessibility text for the Maximize Menu's snap right button [CHAR LIMIT=NONE] -->
<string name="desktop_mode_maximize_menu_snap_right_button_text">Snap right</string>
+
+ <!-- Accessibility text for open by default settings button [CHAR LIMIT=NONE] -->
+ <string name="open_by_default_settings_text">Open by default settings</string>
+ <!-- Subheader for open by default menu string. -->
+ <string name="open_by_default_dialog_subheader_text">Choose how to open web links for this app</string>
+ <!-- Text for open by default settings dialog option. -->
+ <string name="open_by_default_dialog_in_app_text">In the app</string>
+ <!-- Text for open by default settings dialog option. -->
+ <string name="open_by_default_dialog_in_browser_text">In your browser</string>
+ <!-- Text for open by default settings dialog dismiss button. -->
+ <string name="open_by_default_dialog_dismiss_button_text">OK</string>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt
new file mode 100644
index 000000000000..4926cbdbe9fb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.wm.shell.apptoweb
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.TaskInfo
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.PixelFormat
+import android.view.LayoutInflater
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
+import android.view.WindowlessWindowManager
+import android.widget.ImageView
+import android.widget.TextView
+import android.window.TaskConstants
+import com.android.wm.shell.R
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
+import java.util.function.Supplier
+
+
+/**
+ * Window manager for the open by default settings dialog
+ */
+internal class OpenByDefaultDialog(
+ private val context: Context,
+ private val taskInfo: TaskInfo,
+ private val taskSurface: SurfaceControl,
+ private val displayController: DisplayController,
+ private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>,
+ private val listener: DialogLifecycleListener,
+ appIconBitmap: Bitmap?,
+ appName: CharSequence?
+) {
+ private lateinit var dialog: OpenByDefaultDialogView
+ private lateinit var viewHost: SurfaceControlViewHost
+ private lateinit var dialogSurfaceControl: SurfaceControl
+ private var dialogContainer: AdditionalViewHostViewContainer? = null
+ private lateinit var appIconView: ImageView
+ private lateinit var appNameView: TextView
+
+ init {
+ createDialog()
+ bindAppInfo(appIconBitmap, appName)
+ }
+
+ /** Creates an open by default settings dialog. */
+ fun createDialog() {
+ val t = SurfaceControl.Transaction()
+ val taskBounds = taskInfo.configuration.windowConfiguration.bounds
+
+ dialog = LayoutInflater.from(context)
+ .inflate(
+ R.layout.open_by_default_settings_dialog,
+ null /* root */
+ ) as OpenByDefaultDialogView
+ appIconView = dialog.requireViewById(R.id.application_icon)
+ appNameView = dialog.requireViewById(R.id.application_name)
+
+ val display = displayController.getDisplay(taskInfo.displayId)
+ val builder: SurfaceControl.Builder = SurfaceControl.Builder()
+ dialogSurfaceControl = builder
+ .setName("Open by Default Dialog of Task=" + taskInfo.taskId)
+ .setContainerLayer()
+ .setParent(taskSurface)
+ .setCallsite("OpenByDefaultDialog#createDialog")
+ .build()
+ t.setPosition(dialogSurfaceControl, 0f, 0f)
+ .setWindowCrop(dialogSurfaceControl, taskBounds.width(), taskBounds.height())
+ .setLayer(dialogSurfaceControl, TaskConstants.TASK_CHILD_LAYER_SETTINGS_DIALOG)
+ .show(dialogSurfaceControl)
+ val lp = WindowManager.LayoutParams(
+ taskBounds.width(),
+ taskBounds.height(),
+ TYPE_APPLICATION_PANEL,
+ FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCH_MODAL,
+ PixelFormat.TRANSLUCENT)
+ lp.title = "Open by default settings dialog of task=" + taskInfo.taskId
+ lp.setTrustedOverlay()
+ val windowManager = WindowlessWindowManager(
+ taskInfo.configuration,
+ dialogSurfaceControl, null /* hostInputToken */
+ )
+ viewHost = SurfaceControlViewHost(context, display, windowManager, "Dialog").apply {
+ setView(dialog, lp)
+ rootSurfaceControl.applyTransactionOnDraw(t)
+ }
+ dialogContainer = AdditionalViewHostViewContainer(
+ dialogSurfaceControl, viewHost, surfaceControlTransactionSupplier)
+
+ dialog.setDismissOnClickListener{
+ closeMenu()
+ }
+
+ listener.onDialogCreated()
+ }
+
+ private fun closeMenu() {
+ dialogContainer?.releaseView()
+ dialogContainer = null
+ listener.onDialogDismissed()
+ }
+
+ private fun bindAppInfo(
+ appIconBitmap: Bitmap?,
+ appName: CharSequence?
+ ) {
+ appIconView.setImageBitmap(appIconBitmap)
+ appNameView.text = appName
+ }
+
+ /**
+ * Relayout the dialog to the new task bounds.
+ */
+ fun relayout(
+ taskInfo: RunningTaskInfo,
+ ) {
+ val t = surfaceControlTransactionSupplier.get()
+ val taskBounds = taskInfo.configuration.windowConfiguration.bounds
+ t.setWindowCrop(dialogSurfaceControl, taskBounds.width(), taskBounds.height())
+ viewHost.rootSurfaceControl.applyTransactionOnDraw(t)
+ viewHost.relayout(taskBounds.width(), taskBounds.height())
+ }
+
+ /**
+ * Defines interface for classes that can listen to lifecycle events of open by default settings
+ * dialog.
+ */
+ interface DialogLifecycleListener {
+ /** Called when open by default dialog view has been created. */
+ fun onDialogCreated()
+
+ /** Called when open by default dialog view has been released. */
+ fun onDialogDismissed()
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt
new file mode 100644
index 000000000000..d03a38e8699a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.wm.shell.apptoweb
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.View
+import android.widget.Button
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.wm.shell.R
+
+/** View for open by default settings dialog for an application which allows the user to change
+ * where links will open by default, in the default browser or in the application. */
+class OpenByDefaultDialogView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) {
+
+ private lateinit var dialogContainer: View
+ private lateinit var backgroundDim: Drawable
+
+ fun setDismissOnClickListener(callback: (View) -> Unit) {
+ val dismissButton = dialogContainer.requireViewById<Button>(
+ R.id.open_by_default_settings_dialog_dismiss_button)
+ dismissButton.setOnClickListener(callback)
+ // Clicks on the background dim should also dismiss the dialog.
+ setOnClickListener(callback)
+ // We add a no-op on-click listener to the dialog container so that clicks on it won't
+ // propagate to the listener of the layout (which represents the background dim).
+ dialogContainer.setOnClickListener { }
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ dialogContainer = requireViewById(R.id.open_by_default_dialog_container)
+ backgroundDim = background.mutate()
+ backgroundDim.alpha = 128
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 03b7c8b7fe1b..a8a8c2a80974 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -274,7 +274,8 @@ public class BubbleController implements ConfigurationChangeListener,
private final DragAndDropController mDragAndDropController;
/** Used to send bubble events to launcher. */
private Bubbles.BubbleStateListener mBubbleStateListener;
-
+ /** Used to track previous navigation mode to detect switch to buttons navigation. */
+ private boolean mIsPrevNavModeGestures;
/** Used to send updates to the views from {@link #mBubbleDataListener}. */
private BubbleViewCallback mBubbleViewCallback;
@@ -356,6 +357,7 @@ public class BubbleController implements ConfigurationChangeListener,
}
};
mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this);
+ mIsPrevNavModeGestures = ContextUtils.isGestureNavigationMode(mContext);
}
private void registerOneHandedState(OneHandedController oneHanded) {
@@ -589,6 +591,13 @@ public class BubbleController implements ConfigurationChangeListener,
*/
private void sendInitialListenerUpdate() {
if (mBubbleStateListener != null) {
+ boolean isCurrentNavModeGestures = ContextUtils.isGestureNavigationMode(mContext);
+ if (mIsPrevNavModeGestures && !isCurrentNavModeGestures) {
+ BubbleBarLocation navButtonsLocation = ContextUtils.isRtl(mContext)
+ ? BubbleBarLocation.RIGHT : BubbleBarLocation.LEFT;
+ mBubblePositioner.setBubbleBarLocation(navButtonsLocation);
+ }
+ mIsPrevNavModeGestures = isCurrentNavModeGestures;
BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
mBubbleStateListener.onBubbleStateChange(update);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 2795881f0938..35a0d07a63b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -61,7 +61,6 @@ import android.view.ViewOutlineProvider;
import android.view.ViewPropertyAnimator;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
-import android.view.WindowManagerPolicyConstants;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.FrameLayout;
@@ -91,10 +90,10 @@ import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout;
import com.android.wm.shell.bubbles.animation.StackAnimationController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.shared.bubbles.DismissView;
-import com.android.wm.shell.shared.bubbles.RelativeTouchListener;
import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.bubbles.DismissView;
+import com.android.wm.shell.shared.bubbles.RelativeTouchListener;
import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
import java.io.PrintWriter;
@@ -2276,7 +2275,7 @@ public class BubbleStackView extends FrameLayout
void startMonitoringSwipeUpGesture() {
stopMonitoringSwipeUpGestureInternal();
- if (isGestureNavEnabled()) {
+ if (ContextUtils.isGestureNavigationMode(mContext)) {
mBubblesNavBarGestureTracker = new BubblesNavBarGestureTracker(mContext, mPositioner);
mBubblesNavBarGestureTracker.start(mSwipeUpListener);
setOnTouchListener(mContainerSwipeListener);
@@ -2311,12 +2310,6 @@ public class BubbleStackView extends FrameLayout
}
}
- private boolean isGestureNavEnabled() {
- return mContext.getResources().getInteger(
- com.android.internal.R.integer.config_navBarInteractionMode)
- == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
- }
-
/**
* Stop monitoring for swipe up gesture
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt
new file mode 100644
index 000000000000..0b36f452348a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.wm.shell.bubbles
+
+import android.content.Context
+import android.view.View
+import android.view.WindowManagerPolicyConstants
+import com.android.internal.R
+
+/** Simplifies accessing context fields. */
+object ContextUtils {
+
+ /** Gets navigation mode. */
+ @JvmStatic
+ val Context.navigationMode: Int
+ get() = resources.getInteger(R.integer.config_navBarInteractionMode)
+
+ /** Returns whether the navigation mode is gestures. */
+ @JvmStatic
+ val Context.isGestureNavigationMode: Boolean
+ get() = navigationMode == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+
+ /** Returns whether layout direction is rtl. */
+ @JvmStatic
+ val Context.isRtl: Boolean
+ get() = resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 4227a6e2903f..2a5a519272c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -87,7 +87,7 @@ import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler;
import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository;
import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator;
import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
@@ -267,7 +267,7 @@ public abstract class WMShellBaseModule {
Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
Lazy<AccessibilityManager> accessibilityManager,
CompatUIRepository compatUIRepository,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
@NonNull CompatUIState compatUIState,
@NonNull CompatUIComponentIdGenerator componentIdGenerator,
@NonNull CompatUIComponentFactory compatUIComponentFactory,
@@ -281,7 +281,7 @@ public abstract class WMShellBaseModule {
componentIdGenerator, compatUIComponentFactory, mainExecutor));
}
final IntPredicate inDesktopModePredicate =
- desktopModeTaskRepository.<IntPredicate>map(modeTaskRepository -> displayId ->
+ desktopRepository.<IntPredicate>map(modeTaskRepository -> displayId ->
modeTaskRepository.getVisibleTaskCount(displayId) > 0)
.orElseGet(() -> displayId -> false);
return Optional.of(
@@ -707,14 +707,14 @@ public abstract class WMShellBaseModule {
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
TaskStackTransitionObserver taskStackTransitionObserver,
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
RecentTasksController.create(context, shellInit, shellController,
shellCommandHandler, taskStackListener, activityTaskManager,
- desktopModeTaskRepository, taskStackTransitionObserver, mainExecutor));
+ desktopRepository, taskStackTransitionObserver, mainExecutor));
}
@BindsOptionalOf
@@ -1003,16 +1003,16 @@ public abstract class WMShellBaseModule {
@BindsOptionalOf
@DynamicOverride
- abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository();
+ abstract DesktopRepository optionalDesktopRepository();
@WMSingleton
@Provides
- static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository(Context context,
- @DynamicOverride Optional<Lazy<DesktopModeTaskRepository>> desktopModeTaskRepository) {
+ static Optional<DesktopRepository> provideDesktopRepository(Context context,
+ @DynamicOverride Optional<Lazy<DesktopRepository>> desktopRepository) {
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- return desktopModeTaskRepository.flatMap((lazy) -> {
+ return desktopRepository.flatMap((lazy) -> {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(lazy.get());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 72939565ac78..0dca97c8e73c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -69,7 +69,7 @@ import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver;
@@ -245,7 +245,7 @@ public abstract class WMShellModule {
IWindowManager windowManager,
ShellCommandHandler shellCommandHandler,
ShellTaskOrganizer taskOrganizer,
- @DynamicOverride DesktopModeTaskRepository desktopRepository,
+ @DynamicOverride DesktopRepository desktopRepository,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
@@ -352,7 +352,7 @@ public abstract class WMShellModule {
Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
LaunchAdjacentController launchAdjacentController,
WindowDecorViewModel windowDecorViewModel) {
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
@@ -361,7 +361,7 @@ public abstract class WMShellModule {
? shellInit
: null;
return new FreeformTaskListener(context, init, shellTaskOrganizer,
- desktopModeTaskRepository, launchAdjacentController, windowDecorViewModel);
+ desktopRepository, launchAdjacentController, windowDecorViewModel);
}
@WMSingleton
@@ -620,7 +620,7 @@ public abstract class WMShellModule {
DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ @DynamicOverride DesktopRepository desktopRepository,
DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
LaunchAdjacentController launchAdjacentController,
RecentsTransitionHandler recentsTransitionHandler,
@@ -636,7 +636,7 @@ public abstract class WMShellModule {
returnToDragStartAnimator, enterDesktopTransitionHandler,
exitDesktopTransitionHandler, desktopModeDragAndDropTransitionHandler,
toggleResizeDesktopTaskTransitionHandler,
- dragToDesktopTransitionHandler, desktopModeTaskRepository,
+ dragToDesktopTransitionHandler, desktopRepository,
desktopModeLoggerTransitionObserver, launchAdjacentController,
recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter,
recentTasksController.orElse(null), interactionJankMonitor, mainHandler);
@@ -647,7 +647,7 @@ public abstract class WMShellModule {
static Optional<DesktopTasksLimiter> provideDesktopTasksLimiter(
Context context,
Transitions transitions,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ @DynamicOverride DesktopRepository desktopRepository,
ShellTaskOrganizer shellTaskOrganizer,
InteractionJankMonitor interactionJankMonitor,
@ShellMainThread Handler handler) {
@@ -660,7 +660,7 @@ public abstract class WMShellModule {
return Optional.of(
new DesktopTasksLimiter(
transitions,
- desktopModeTaskRepository,
+ desktopRepository,
shellTaskOrganizer,
maxTaskLimit,
interactionJankMonitor,
@@ -739,13 +739,14 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
@DynamicOverride
- static DesktopModeTaskRepository provideDesktopModeTaskRepository(
+
+ static DesktopRepository provideDesktopRepository(
Context context,
ShellInit shellInit,
DesktopPersistentRepository desktopPersistentRepository,
@ShellMainThread CoroutineScope mainScope
) {
- return new DesktopModeTaskRepository(context, shellInit, desktopPersistentRepository,
+ return new DesktopRepository(context, shellInit, desktopPersistentRepository,
mainScope);
}
@@ -757,12 +758,12 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
TaskStackListenerImpl taskStackListener,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository
+ @DynamicOverride DesktopRepository desktopRepository
) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(new DesktopActivityOrientationChangeHandler(
context, shellInit, shellTaskOrganizer, taskStackListener,
- toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository));
+ toggleResizeDesktopTaskTransitionHandler, desktopRepository));
}
return Optional.empty();
}
@@ -771,12 +772,12 @@ public abstract class WMShellModule {
@Provides
static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver(
Context context,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
Transitions transitions,
ShellTaskOrganizer shellTaskOrganizer,
ShellInit shellInit
) {
- return desktopModeTaskRepository.flatMap(repository ->
+ return desktopRepository.flatMap(repository ->
Optional.of(new DesktopTasksTransitionObserver(
context, repository, transitions, shellTaskOrganizer, shellInit))
);
@@ -787,7 +788,7 @@ public abstract class WMShellModule {
static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
Context context,
Transitions transitions,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ @DynamicOverride DesktopRepository desktopRepository,
FreeformTaskTransitionHandler freeformTaskTransitionHandler,
CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
InteractionJankMonitor interactionJankMonitor,
@@ -801,7 +802,7 @@ public abstract class WMShellModule {
new DesktopMixedTransitionHandler(
context,
transitions,
- desktopModeTaskRepository,
+ desktopRepository,
freeformTaskTransitionHandler,
closeDesktopTaskTransitionHandler,
interactionJankMonitor,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
index 59e006879da8..606aa6cd3353 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
@@ -39,7 +39,7 @@ class DesktopActivityOrientationChangeHandler(
private val shellTaskOrganizer: ShellTaskOrganizer,
private val taskStackListener: TaskStackListenerImpl,
private val resizeHandler: ToggleResizeDesktopTaskTransitionHandler,
- private val taskRepository: DesktopModeTaskRepository,
+ private val taskRepository: DesktopRepository,
) {
init {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index ec3f8c5fc2f8..435019929cbd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -40,7 +40,7 @@ import com.android.wm.shell.transition.Transitions
class DesktopMixedTransitionHandler(
private val context: Context,
private val transitions: Transitions,
- private val desktopTaskRepository: DesktopModeTaskRepository,
+ private val desktopRepository: DesktopRepository,
private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler,
private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler,
private val interactionJankMonitor: InteractionJankMonitor,
@@ -138,7 +138,7 @@ class DesktopMixedTransitionHandler(
private fun isLastDesktopTask(change: TransitionInfo.Change): Boolean =
change.taskInfo?.let {
- desktopTaskRepository.getActiveNonMinimizedTaskCount(it.displayId) == 1
+ desktopRepository.getActiveNonMinimizedTaskCount(it.displayId) == 1
} ?: false
private fun findCloseDesktopTaskChange(info: TransitionInfo): TransitionInfo.Change? {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index cca750014fc1..73e55b2c4792 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -36,7 +36,7 @@ public interface DesktopMode {
* @param listener the listener to add.
* @param callbackExecutor the executor to call the listener on.
*/
- void addVisibleTasksListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+ void addVisibleTasksListener(DesktopRepository.VisibleTasksListener listener,
Executor callbackExecutor);
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 72619195fb3f..52b92a89abdf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -21,6 +21,12 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
@@ -125,7 +131,7 @@ public class DesktopModeVisualIndicator {
mContext = context;
mTaskSurface = taskSurface;
mRootTdaOrganizer = taskDisplayAreaOrganizer;
- mCurrentType = IndicatorType.NO_INDICATOR;
+ mCurrentType = NO_INDICATOR;
mDragStartState = dragStartState;
}
@@ -136,8 +142,16 @@ public class DesktopModeVisualIndicator {
@NonNull
IndicatorType updateIndicatorType(PointF inputCoordinates) {
final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ // Perform a quick check first: any input off the left edge of the display should be split
+ // left, and split right for the right edge. This is universal across all drag event types.
+ if (inputCoordinates.x < 0) return TO_SPLIT_LEFT_INDICATOR;
+ if (inputCoordinates.x > layout.width()) return TO_SPLIT_RIGHT_INDICATOR;
// If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
- IndicatorType result = IndicatorType.NO_INDICATOR;
+ // In drags not originating on a freeform caption, we should default to a TO_DESKTOP
+ // indicator.
+ IndicatorType result = mDragStartState == DragStartState.FROM_FREEFORM
+ ? NO_INDICATOR
+ : TO_DESKTOP_INDICATOR;
final int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_transition_region_thickness);
// Because drags in freeform use task position for indicator calculation, we need to
@@ -149,10 +163,8 @@ public class DesktopModeVisualIndicator {
captionHeight);
final Region splitRightRegion = calculateSplitRightRegion(layout, transitionAreaWidth,
captionHeight);
- final Region toDesktopRegion = calculateToDesktopRegion(layout, splitLeftRegion,
- splitRightRegion, fullscreenRegion);
if (fullscreenRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
- result = IndicatorType.TO_FULLSCREEN_INDICATOR;
+ result = TO_FULLSCREEN_INDICATOR;
}
if (splitLeftRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
result = IndicatorType.TO_SPLIT_LEFT_INDICATOR;
@@ -160,9 +172,6 @@ public class DesktopModeVisualIndicator {
if (splitRightRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
result = IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
}
- if (toDesktopRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
- result = IndicatorType.TO_DESKTOP_INDICATOR;
- }
if (mDragStartState != DragStartState.DRAGGED_INTENT) {
transitionIndicator(result);
}
@@ -182,7 +191,7 @@ public class DesktopModeVisualIndicator {
R.dimen.desktop_mode_fullscreen_region_scale);
final float toFullscreenWidth = (layout.width() * toFullscreenScale);
region.union(new Rect((int) ((layout.width() / 2f) - (toFullscreenWidth / 2f)),
- -captionHeight,
+ Short.MIN_VALUE,
(int) ((layout.width() / 2f) + (toFullscreenWidth / 2f)),
transitionHeight));
}
@@ -192,7 +201,7 @@ public class DesktopModeVisualIndicator {
|| mDragStartState == DragStartState.DRAGGED_INTENT
) {
region.union(new Rect(0,
- -captionHeight,
+ Short.MIN_VALUE,
layout.width(),
transitionHeight));
}
@@ -200,21 +209,6 @@ public class DesktopModeVisualIndicator {
}
@VisibleForTesting
- Region calculateToDesktopRegion(DisplayLayout layout,
- Region splitLeftRegion, Region splitRightRegion,
- Region toFullscreenRegion) {
- final Region region = new Region();
- // If in desktop, we need no region. Otherwise it's the same for all windowing modes.
- if (mDragStartState != DragStartState.FROM_FREEFORM) {
- region.union(new Rect(0, 0, layout.width(), layout.height()));
- region.op(splitLeftRegion, Region.Op.DIFFERENCE);
- region.op(splitRightRegion, Region.Op.DIFFERENCE);
- region.op(toFullscreenRegion, Region.Op.DIFFERENCE);
- }
- return region;
- }
-
- @VisibleForTesting
Region calculateSplitLeftRegion(DisplayLayout layout,
int transitionEdgeWidth, int captionHeight) {
final Region region = new Region();
@@ -311,7 +305,7 @@ public class DesktopModeVisualIndicator {
}
});
}
- mCurrentType = IndicatorType.NO_INDICATOR;
+ mCurrentType = NO_INDICATOR;
}
/**
@@ -322,9 +316,9 @@ public class DesktopModeVisualIndicator {
if (mView == null) {
createView();
}
- if (mCurrentType == IndicatorType.NO_INDICATOR) {
+ if (mCurrentType == NO_INDICATOR) {
fadeInIndicator(newType);
- } else if (newType == IndicatorType.NO_INDICATOR) {
+ } else if (newType == NO_INDICATOR) {
fadeOutIndicator(null /* finishCallback */);
} else {
final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 985224e30a51..7b2a5d343861 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -42,8 +42,8 @@ import java.util.function.Consumer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
-/** Tracks task data for Desktop Mode. */
-class DesktopModeTaskRepository (
+/** Tracks desktop data for Android Desktop Windowing. */
+class DesktopRepository (
private val context: Context,
shellInit: ShellInit,
private val persistentRepository: DesktopPersistentRepository,
@@ -467,10 +467,9 @@ class DesktopModeTaskRepository (
}
}
-
internal fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
- pw.println("${prefix}DesktopModeTaskRepository")
+ pw.println("${prefix}DesktopRepository")
dumpDesktopTaskData(pw, innerPrefix)
pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}")
pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}")
@@ -512,10 +511,9 @@ class DesktopModeTaskRepository (
}
companion object {
- private const val TAG = "DesktopModeTaskRepository"
+ private const val TAG = "DesktopRepository"
}
}
private fun <T> Iterable<T>.toDumpString(): String =
joinToString(separator = ", ", prefix = "[", postfix = "]")
-
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index fcd2f8ceaf49..2303e71eced2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -73,7 +73,7 @@ import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
@@ -134,7 +134,7 @@ class DesktopTasksController(
private val desktopModeDragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler,
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
- private val taskRepository: DesktopModeTaskRepository,
+ private val taskRepository: DesktopRepository,
private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver,
private val launchAdjacentController: LaunchAdjacentController,
private val recentsTransitionHandler: RecentsTransitionHandler,
@@ -1594,7 +1594,7 @@ class DesktopTasksController(
return
}
- val indicator = visualIndicator ?: return
+ val indicator = getVisualIndicator() ?: return
val indicatorType =
indicator.updateIndicatorType(
PointF(inputCoordinate.x, currentDragBounds.top.toFloat()),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 37ad0c9f72c5..37bec21730a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -44,7 +44,7 @@ import com.android.wm.shell.transition.Transitions.TransitionObserver
*/
class DesktopTasksLimiter (
transitions: Transitions,
- private val taskRepository: DesktopModeTaskRepository,
+ private val taskRepository: DesktopRepository,
private val shellTaskOrganizer: ShellTaskOrganizer,
private val maxTasksLimit: Int,
private val interactionJankMonitor: InteractionJankMonitor,
@@ -160,7 +160,7 @@ class DesktopTasksLimiter (
}
@VisibleForTesting
- inner class LeftoverMinimizedTasksRemover : DesktopModeTaskRepository.ActiveTasksListener {
+ inner class LeftoverMinimizedTasksRemover : DesktopRepository.ActiveTasksListener {
override fun onActiveTasksChanged(displayId: Int) {
// If back navigation is enabled, we shouldn't remove the leftover tasks
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index b20c9fc6e443..e086e40fb21c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -36,12 +36,12 @@ import com.android.wm.shell.transition.Transitions
/**
* A [Transitions.TransitionObserver] that observes shell transitions and updates the
- * [DesktopModeTaskRepository] state TODO: b/332682201 This observes transitions related to desktop
+ * [DesktopRepository] state TODO: b/332682201 This observes transitions related to desktop
* mode and other transitions that originate both within and outside shell.
*/
class DesktopTasksTransitionObserver(
private val context: Context,
- private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ private val desktopRepository: DesktopRepository,
private val transitions: Transitions,
private val shellTaskOrganizer: ShellTaskOrganizer,
shellInit: ShellInit
@@ -83,10 +83,10 @@ class DesktopTasksTransitionObserver(
val taskInfo = change.taskInfo
if (taskInfo == null || taskInfo.taskId == -1) continue
- if (desktopModeTaskRepository.isActiveTask(taskInfo.taskId)
+ if (desktopRepository.isActiveTask(taskInfo.taskId)
&& taskInfo.windowingMode != WINDOWING_MODE_FREEFORM
) {
- desktopModeTaskRepository.removeFreeformTask(
+ desktopRepository.removeFreeformTask(
taskInfo.displayId,
taskInfo.taskId
)
@@ -104,11 +104,11 @@ class DesktopTasksTransitionObserver(
continue
}
- if (desktopModeTaskRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
+ if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
change.mode == TRANSIT_TO_BACK &&
taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
) {
- desktopModeTaskRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+ desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
}
}
}
@@ -135,7 +135,7 @@ class DesktopTasksTransitionObserver(
if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) {
when (change.mode) {
WindowManager.TRANSIT_OPEN -> {
- desktopModeTaskRepository.wallpaperActivityToken = taskInfo.token
+ desktopRepository.wallpaperActivityToken = taskInfo.token
// After the task for the wallpaper is created, set it non-trimmable.
// This is important to prevent recents from trimming and removing the
// task.
@@ -145,7 +145,7 @@ class DesktopTasksTransitionObserver(
)
}
WindowManager.TRANSIT_CLOSE ->
- desktopModeTaskRepository.wallpaperActivityToken = null
+ desktopRepository.wallpaperActivityToken = null
else -> {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 7f7f10519bb1..73f70118b07d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -29,7 +29,7 @@ import android.window.flags.DesktopModeFlags;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.LaunchAdjacentController;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
@@ -49,7 +49,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
private final Context mContext;
private final ShellTaskOrganizer mShellTaskOrganizer;
- private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
+ private final Optional<DesktopRepository> mDesktopRepository;
private final WindowDecorViewModel mWindowDecorationViewModel;
private final LaunchAdjacentController mLaunchAdjacentController;
@@ -64,13 +64,13 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
LaunchAdjacentController launchAdjacentController,
WindowDecorViewModel windowDecorationViewModel) {
mContext = context;
mShellTaskOrganizer = shellTaskOrganizer;
mWindowDecorationViewModel = windowDecorationViewModel;
- mDesktopModeTaskRepository = desktopModeTaskRepository;
+ mDesktopRepository = desktopRepository;
mLaunchAdjacentController = launchAdjacentController;
if (shellInit != null) {
shellInit.addInitCallback(this::onInit, this);
@@ -102,7 +102,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
}
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopModeTaskRepository.ifPresent(repository -> {
+ mDesktopRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
if (taskInfo.isVisible) {
repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
@@ -121,7 +121,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
mTasks.remove(taskInfo.taskId);
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopModeTaskRepository.ifPresent(repository -> {
+ mDesktopRepository.ifPresent(repository -> {
// TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()
|| repository.isClosingTask(taskInfo.taskId)) {
@@ -150,7 +150,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
mWindowDecorationViewModel.onTaskInfoChanged(taskInfo);
state.mTaskInfo = taskInfo;
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopModeTaskRepository.ifPresent(repository -> {
+ mDesktopRepository.ifPresent(repository -> {
if (taskInfo.isVisible) {
repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
} else if (repository.isClosingTask(taskInfo.taskId)) {
@@ -182,7 +182,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
"Freeform Task Focus Changed: #%d focused=%b",
taskInfo.taskId, taskInfo.isFocused);
if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused) {
- mDesktopModeTaskRepository.ifPresent(repository -> {
+ mDesktopRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index c9c0873dae5e..95cb3df50977 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -54,7 +54,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.annotations.ExternalThread;
@@ -81,14 +81,14 @@ import java.util.function.Consumer;
* Manages the recent task list from the system, caching it as necessary.
*/
public class RecentTasksController implements TaskStackListenerCallback,
- RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.ActiveTasksListener,
+ RemoteCallable<RecentTasksController>, DesktopRepository.ActiveTasksListener,
TaskStackTransitionObserver.TaskStackTransitionObserverListener {
private static final String TAG = RecentTasksController.class.getSimpleName();
private final Context mContext;
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
- private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
+ private final Optional<DesktopRepository> mDesktopRepository;
private final ShellExecutor mMainExecutor;
private final TaskStackListenerImpl mTaskStackListener;
private final RecentTasksImpl mImpl = new RecentTasksImpl();
@@ -121,7 +121,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
TaskStackTransitionObserver taskStackTransitionObserver,
@ShellMainThread ShellExecutor mainExecutor
) {
@@ -129,7 +129,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
return null;
}
return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
- taskStackListener, activityTaskManager, desktopModeTaskRepository,
+ taskStackListener, activityTaskManager, desktopRepository,
taskStackTransitionObserver, mainExecutor);
}
@@ -139,7 +139,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
TaskStackTransitionObserver taskStackTransitionObserver,
ShellExecutor mainExecutor) {
mContext = context;
@@ -148,7 +148,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
mActivityTaskManager = activityTaskManager;
mPcFeatureEnabled = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
mTaskStackListener = taskStackListener;
- mDesktopModeTaskRepository = desktopModeTaskRepository;
+ mDesktopRepository = desktopRepository;
mTaskStackTransitionObserver = taskStackTransitionObserver;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
@@ -168,7 +168,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
mTaskStackListener.addListener(this);
- mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this));
+ mDesktopRepository.ifPresent(it -> it.addActiveTaskListener(this));
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTaskStackTransitionObserver.addTaskStackTransitionObserverListener(this,
mMainExecutor);
@@ -417,8 +417,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
if (DesktopModeStatus.canEnterDesktopMode(mContext)
- && mDesktopModeTaskRepository.isPresent()
- && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
+ && mDesktopRepository.isPresent()
+ && mDesktopRepository.get().isActiveTask(taskInfo.taskId)) {
// Freeform tasks will be added as a separate entry
if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
mostRecentFreeformTaskIndex = recentTasks.size();
@@ -434,7 +434,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
taskInfo.lastNonFullscreenBounds.top);
}
freeformTasks.add(taskInfo);
- if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) {
+ if (mDesktopRepository.get().isMinimizedTask(taskInfo.taskId)) {
minimizedFreeformTasks.add(taskInfo.taskId);
}
continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 3330f968332c..bf175b71ca05 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -103,7 +103,7 @@ import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -155,7 +155,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final ActivityTaskManager mActivityTaskManager;
private final ShellCommandHandler mShellCommandHandler;
private final ShellTaskOrganizer mTaskOrganizer;
- private final DesktopModeTaskRepository mDesktopRepository;
+ private final DesktopRepository mDesktopRepository;
private final ShellController mShellController;
private final Context mContext;
private final @ShellMainThread Handler mMainHandler;
@@ -227,7 +227,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
ShellCommandHandler shellCommandHandler,
IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
@@ -288,7 +288,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
ShellCommandHandler shellCommandHandler,
IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index ada1e0b8b1d5..c7e842244de2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -87,13 +87,14 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
import com.android.wm.shell.apptoweb.AppToWebUtils;
import com.android.wm.shell.apptoweb.AssistContentRequester;
+import com.android.wm.shell.apptoweb.OpenByDefaultDialog;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.CaptionState;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -163,6 +164,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private MaximizeMenu mMaximizeMenu;
+ private OpenByDefaultDialog mOpenByDefaultDialog;
+
private ResizeVeil mResizeVeil;
private Bitmap mAppIconBitmap;
private Bitmap mResizeVeilBitmap;
@@ -193,14 +196,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
private final MultiInstanceHelper mMultiInstanceHelper;
private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
- private final DesktopModeTaskRepository mDesktopRepository;
+ private final DesktopRepository mDesktopRepository;
DesktopModeWindowDecoration(
Context context,
@NonNull Context userContext,
DisplayController displayController,
SplitScreenController splitScreenController,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
@@ -232,7 +235,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@NonNull Context userContext,
DisplayController displayController,
SplitScreenController splitScreenController,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
@@ -448,6 +451,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mHandleMenu.relayout(startT, mResult.mCaptionX);
}
+ if (isOpenByDefaultDialogActive()) {
+ mOpenByDefaultDialog.relayout(taskInfo);
+ }
+
final boolean inFullImmersive = mDesktopRepository
.isTaskInFullImmersiveState(taskInfo.taskId);
updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
@@ -940,6 +947,33 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return mHandleMenu != null;
}
+ boolean isOpenByDefaultDialogActive() {
+ return mOpenByDefaultDialog != null;
+ }
+
+ void createOpenByDefaultDialog() {
+ mOpenByDefaultDialog = new OpenByDefaultDialog(
+ mContext,
+ mTaskInfo,
+ mTaskSurface,
+ mDisplayController,
+ mSurfaceControlTransactionSupplier,
+ new OpenByDefaultDialog.DialogLifecycleListener() {
+ @Override
+ public void onDialogCreated() {
+ closeHandleMenu();
+ }
+
+ @Override
+ public void onDialogDismissed() {
+ mOpenByDefaultDialog = null;
+ }
+ },
+ mAppIconBitmap,
+ mAppName
+ );
+ }
+
boolean shouldResizeListenerHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
return mDragResizeListener != null && mDragResizeListener.shouldHandleEvent(e, offset);
}
@@ -1236,6 +1270,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
onCapturedLinkExpired();
return Unit.INSTANCE;
},
+ /* onOpenByDefaultClickListener= */ () -> {
+ if (!isOpenByDefaultDialogActive()) {
+ createOpenByDefaultDialog();
+ }
+ return Unit.INSTANCE;
+ },
/* onCloseMenuClickListener= */ () -> {
closeHandleMenu();
return Unit.INSTANCE;
@@ -1591,7 +1631,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@NonNull Context userContext,
DisplayController displayController,
SplitScreenController splitScreenController,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 98fef4722d1c..2e327035bddf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -120,6 +120,7 @@ class HandleMenu(
onNewWindowClickListener: () -> Unit,
onManageWindowsClickListener: () -> Unit,
openInBrowserClickListener: (Intent) -> Unit,
+ onOpenByDefaultClickListener: () -> Unit,
onCloseMenuClickListener: () -> Unit,
onOutsideTouchListener: () -> Unit,
) {
@@ -135,6 +136,7 @@ class HandleMenu(
onNewWindowClickListener = onNewWindowClickListener,
onManageWindowsClickListener = onManageWindowsClickListener,
openInBrowserClickListener = openInBrowserClickListener,
+ onOpenByDefaultClickListener = onOpenByDefaultClickListener,
onCloseMenuClickListener = onCloseMenuClickListener,
onOutsideTouchListener = onOutsideTouchListener,
)
@@ -153,6 +155,7 @@ class HandleMenu(
onNewWindowClickListener: () -> Unit,
onManageWindowsClickListener: () -> Unit,
openInBrowserClickListener: (Intent) -> Unit,
+ onOpenByDefaultClickListener: () -> Unit,
onCloseMenuClickListener: () -> Unit,
onOutsideTouchListener: () -> Unit
) {
@@ -174,6 +177,7 @@ class HandleMenu(
this.onOpenInBrowserClickListener = {
openInBrowserClickListener.invoke(openInBrowserIntent!!)
}
+ this.onOpenByDefaultClickListener = onOpenByDefaultClickListener
this.onCloseMenuClickListener = onCloseMenuClickListener
this.onOutsideTouchListener = onOutsideTouchListener
}
@@ -448,7 +452,8 @@ class HandleMenu(
private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill)
private val browserBtn = openInBrowserPill.requireViewById<Button>(
R.id.open_in_browser_button)
-
+ private val openByDefaultBtn = openInBrowserPill.requireViewById<ImageButton>(
+ R.id.open_by_default_button)
private val decorThemeUtil = DecorThemeUtil(context)
private val animator = HandleMenuAnimator(rootView, menuWidth, captionHeight.toFloat())
@@ -461,6 +466,7 @@ class HandleMenu(
var onNewWindowClickListener: (() -> Unit)? = null
var onManageWindowsClickListener: (() -> Unit)? = null
var onOpenInBrowserClickListener: (() -> Unit)? = null
+ var onOpenByDefaultClickListener: (() -> Unit)? = null
var onCloseMenuClickListener: (() -> Unit)? = null
var onOutsideTouchListener: (() -> Unit)? = null
@@ -469,6 +475,9 @@ class HandleMenu(
splitscreenBtn.setOnClickListener { onToSplitScreenClickListener?.invoke() }
desktopBtn.setOnClickListener { onToDesktopClickListener?.invoke() }
browserBtn.setOnClickListener { onOpenInBrowserClickListener?.invoke() }
+ openByDefaultBtn.setOnClickListener {
+ onOpenByDefaultClickListener?.invoke()
+ }
collapseMenuButton.setOnClickListener { onCloseMenuClickListener?.invoke() }
newWindowBtn.setOnClickListener { onNewWindowClickListener?.invoke() }
manageWindowBtn.setOnClickListener { onManageWindowsClickListener?.invoke() }
@@ -634,6 +643,8 @@ class HandleMenu(
setTextColor(style.textColor)
compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
}
+
+ openByDefaultBtn.imageTintList = ColorStateList.valueOf(style.textColor)
}
private data class MenuStyle(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index 628c9cdd9339..3e9c732b9c3b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -98,7 +98,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var handler: DesktopActivityOrientationChangeHandler
private lateinit var shellInit: ShellInit
- private lateinit var taskRepository: DesktopModeTaskRepository
+ private lateinit var taskRepository: DesktopRepository
private lateinit var testScope: CoroutineScope
// Mock running tasks are registered here so we can get the list from mock shell task organizer.
private val runningTasks = mutableListOf<RunningTaskInfo>()
@@ -116,7 +116,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
taskRepository =
- DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
+ DesktopRepository(context, shellInit, persistentRepository, testScope)
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index 2b60200f06ad..07de0716200c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -60,7 +60,7 @@ import org.mockito.kotlin.whenever
class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@Mock lateinit var transitions: Transitions
- @Mock lateinit var desktopTaskRepository: DesktopModeTaskRepository
+ @Mock lateinit var desktopRepository: DesktopRepository
@Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
@Mock lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler
@Mock lateinit var interactionJankMonitor: InteractionJankMonitor
@@ -75,7 +75,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
DesktopMixedTransitionHandler(
context,
transitions,
- desktopTaskRepository,
+ desktopRepository,
freeformTaskTransitionHandler,
closeDesktopTaskTransitionHandler,
interactionJankMonitor,
@@ -144,7 +144,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
fun startAnimation_withClosingDesktopTask_callsCloseTaskHandler() {
val transition = mock<IBinder>()
val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
- whenever(desktopTaskRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(2)
+ whenever(desktopRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(2)
whenever(
closeDesktopTaskTransitionHandler.startAnimation(any(), any(), any(), any(), any())
)
@@ -167,7 +167,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
fun startAnimation_withClosingLastDesktopTask_dispatchesTransition() {
val transition = mock<IBinder>()
val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
- whenever(desktopTaskRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(1)
+ whenever(desktopRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(1)
whenever(transitions.dispatchTransition(any(), any(), any(), any(), any(), any()))
.thenReturn(mock())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index 2b7f86f36477..935e6d052f5e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -17,8 +17,8 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.PointF
import android.graphics.Rect
-import android.graphics.Region
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl
import androidx.test.filters.SmallTest
@@ -33,6 +33,7 @@ import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.kotlin.whenever
@@ -58,13 +59,15 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
+ whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
}
@Test
fun testFullscreenRegionCalculation() {
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400,
+ 2 * STABLE_INSETS.top))
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
@@ -75,17 +78,19 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
val toFullscreenWidth = displayLayout.width() * toFullscreenScale
assertThat(testRegion.bounds).isEqualTo(Rect(
(DISPLAY_BOUNDS.width() / 2f - toFullscreenWidth / 2f).toInt(),
- -50,
+ Short.MIN_VALUE.toInt(),
(DISPLAY_BOUNDS.width() / 2f + toFullscreenWidth / 2f).toInt(),
transitionHeight))
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400,
+ 2 * STABLE_INSETS.top))
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400,
+ transitionHeight))
}
@Test
@@ -133,22 +138,19 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
}
@Test
- fun testToDesktopRegionCalculation() {
+ fun testDefaultIndicators() {
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
- val fullscreenRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
- CAPTION_HEIGHT)
- val splitLeftRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
- TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
- val splitRightRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
- TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
- val desktopRegion = visualIndicator.calculateToDesktopRegion(displayLayout,
- splitLeftRegion, splitRightRegion, fullscreenRegion)
- var testRegion = Region()
- testRegion.union(DISPLAY_BOUNDS)
- testRegion.op(splitLeftRegion, Region.Op.DIFFERENCE)
- testRegion.op(splitRightRegion, Region.Op.DIFFERENCE)
- testRegion.op(fullscreenRegion, Region.Op.DIFFERENCE)
- assertThat(desktopRegion).isEqualTo(testRegion)
+ var result = visualIndicator.updateIndicatorType(PointF(-10000f, 500f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+ result = visualIndicator.updateIndicatorType(PointF(10000f, 500f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
+ result = visualIndicator.updateIndicatorType(PointF(500f, 10000f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
+ result = visualIndicator.updateIndicatorType(PointF(500f, 10000f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
}
private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 97ceecc3662d..55b9724795e1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -56,9 +56,9 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@ExperimentalCoroutinesApi
-class DesktopModeTaskRepositoryTest : ShellTestCase() {
+class DesktopRepositoryTest : ShellTestCase() {
- private lateinit var repo: DesktopModeTaskRepository
+ private lateinit var repo: DesktopRepository
private lateinit var shellInit: ShellInit
private lateinit var datastoreScope: CoroutineScope
@@ -71,7 +71,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- repo = DesktopModeTaskRepository(context, shellInit, persistentRepository, datastoreScope)
+ repo = DesktopRepository(context, shellInit, persistentRepository, datastoreScope)
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
Desktop.getDefaultInstance()
)
@@ -940,7 +940,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
assertThat(repo.isTaskInFullImmersiveState(taskId = 2)).isTrue()
}
- class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
+ class TestListener : DesktopRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
override fun onActiveTasksChanged(displayId: Int) {
@@ -952,7 +952,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
}
}
- class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener {
+ class TestVisibilityListener : DesktopRepository.VisibleTasksListener {
var visibleTasksCountOnDefaultDisplay = 0
var visibleTasksCountOnSecondaryDisplay = 0
@@ -980,4 +980,4 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
private const val DEFAULT_USER_ID = 1000
private const val DEFAULT_DESKTOP_ID = 0
}
-}
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 2ddb1acf7300..0ccd160c7a91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -201,7 +201,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
- private lateinit var taskRepository: DesktopModeTaskRepository
+ private lateinit var taskRepository: DesktopRepository
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
private lateinit var testScope: CoroutineScope
@@ -232,7 +232,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- taskRepository = DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
+ taskRepository = DesktopRepository(context, shellInit, persistentRepository, testScope)
desktopTasksLimiter =
DesktopTasksLimiter(
transitions,
@@ -2643,13 +2643,17 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() {
val task = setUpFreeformTask()
+ val spyController = spy(controller)
val mockSurface = mock(SurfaceControl::class.java)
val mockDisplayLayout = mock(DisplayLayout::class.java)
whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
- controller.onDragPositioningMove(task, mockSurface, 200f, Rect(100, -100, 500, 1000))
+ spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, -100, 500, 1000))
- controller.onDragPositioningEnd(
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+ spyController.onDragPositioningEnd(
task,
mockSurface,
Point(100, -100), /* position */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index bdcb459d9461..596b76dbdb2e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -92,7 +92,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
- private lateinit var desktopTaskRepo: DesktopModeTaskRepository
+ private lateinit var desktopTaskRepo: DesktopRepository
private lateinit var shellInit: ShellInit
private lateinit var testScope: CoroutineScope
@@ -106,7 +106,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
desktopTaskRepo =
- DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
+ DesktopRepository(context, shellInit, persistentRepository, testScope)
desktopTasksLimiter =
DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT,
interactionJankMonitor, mContext, handler)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 42fcc836bac3..598df34a310d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -62,7 +62,7 @@ class DesktopTasksTransitionObserverTest {
private val transitions = mock<Transitions>()
private val context = mock<Context>()
private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
- private val taskRepository = mock<DesktopModeTaskRepository>()
+ private val taskRepository = mock<DesktopRepository>()
private lateinit var transitionObserver: DesktopTasksTransitionObserver
private lateinit var shellInit: ShellInit
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
index 5596ad7726f1..1e105d9588ab 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
@@ -46,6 +46,7 @@ import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.Before
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -61,10 +62,7 @@ import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-/**
- * Tests of [AppHandleEducationController]
- * Usage: atest AppHandleEducationControllerTest
- */
+/** Tests of [AppHandleEducationController] Usage: atest AppHandleEducationControllerTest */
@SmallTest
@RunWith(AndroidTestingRunner::class)
@OptIn(ExperimentalCoroutinesApi::class)
@@ -220,6 +218,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showWindowingImageButtonTooltip_appHandleExpanded_shouldCallShowEducationTooltipTwice() =
testScope.runTest {
// After first tooltip is dismissed, app handle is expanded. Should show second education
@@ -237,6 +236,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showWindowingImageButtonTooltip_appHandleExpandedAfterTimeout_shouldCallShowEducationTooltipOnce() =
testScope.runTest {
// After first tooltip is dismissed, app handle is expanded after timeout. Should not show
@@ -258,6 +258,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showWindowingImageButtonTooltip_appHandleExpandedTwice_shouldCallShowEducationTooltipTwice() =
testScope.runTest {
// After first tooltip is dismissed, app handle is expanded twice. Should show second
@@ -279,6 +280,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showWindowingImageButtonTooltip_appHandleNotExpanded_shouldCallShowEducationTooltipOnce() =
testScope.runTest {
// After first tooltip is dismissed, app handle is not expanded. Should not show second
@@ -296,6 +298,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showExitWindowingButtonTooltip_appHeaderVisible_shouldCallShowEducationTooltipThrice() =
testScope.runTest {
// After first two tooltips are dismissed, app header is visible. Should show third
@@ -313,6 +316,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showExitWindowingButtonTooltip_appHeaderVisibleAfterTimeout_shouldCallShowEducationTooltipTwice() =
testScope.runTest {
// After first two tooltips are dismissed, app header is visible after timeout. Should not
@@ -334,6 +338,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showExitWindowingButtonTooltip_appHeaderVisibleTwice_shouldCallShowEducationTooltipThrice() =
testScope.runTest {
// After first two tooltips are dismissed, app header is visible twice. Should show third
@@ -354,6 +359,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showExitWindowingButtonTooltip_appHeaderExpanded_shouldCallShowEducationTooltipTwice() =
testScope.runTest {
// After first two tooltips are dismissed, app header is visible but expanded. Should not
@@ -393,6 +399,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun setAppHandleEducationTooltipCallbacks_onWindowingImageButtonTooltipClicked_callbackInvoked() =
testScope.runTest {
// After first tooltip is dismissed, app handle is expanded. Should show second education
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 3b2c7e6eaf99..956ef14bf2ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -41,7 +41,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.LaunchAdjacentController;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -73,7 +73,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
@Mock
private SurfaceControl mMockSurfaceControl;
@Mock
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ private DesktopRepository mDesktopRepository;
@Mock
private LaunchAdjacentController mLaunchAdjacentController;
private FreeformTaskListener mFreeformTaskListener;
@@ -89,7 +89,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mContext,
mShellInit,
mTaskOrganizer,
- Optional.of(mDesktopModeTaskRepository),
+ Optional.of(mDesktopRepository),
mLaunchAdjacentController,
mWindowDecorViewModel);
}
@@ -102,7 +102,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mFreeformTaskListener.onFocusTaskChanged(task);
- verify(mDesktopModeTaskRepository)
+ verify(mDesktopRepository)
.addOrMoveFreeformTaskToTop(task.displayId, task.taskId);
}
@@ -114,7 +114,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mFreeformTaskListener.onFocusTaskChanged(fullscreenTask);
- verify(mDesktopModeTaskRepository, never())
+ verify(mDesktopRepository, never())
.addOrMoveFreeformTaskToTop(fullscreenTask.displayId, fullscreenTask.taskId);
}
@@ -156,7 +156,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
task.displayId = INVALID_DISPLAY;
mFreeformTaskListener.onTaskVanished(task);
- verify(mDesktopModeTaskRepository).minimizeTask(task.displayId, task.taskId);
+ verify(mDesktopRepository).minimizeTask(task.displayId, task.taskId);
}
@Test
@@ -168,13 +168,13 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
- when(mDesktopModeTaskRepository.isClosingTask(task.taskId)).thenReturn(true);
+ when(mDesktopRepository.isClosingTask(task.taskId)).thenReturn(true);
task.isVisible = false;
task.displayId = INVALID_DISPLAY;
mFreeformTaskListener.onTaskVanished(task);
- verify(mDesktopModeTaskRepository, never()).minimizeTask(task.displayId, task.taskId);
- verify(mDesktopModeTaskRepository).removeFreeformTask(task.displayId, task.taskId);
+ verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId);
+ verify(mDesktopRepository).removeFreeformTask(task.displayId, task.taskId);
}
@After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 0364b51fee1c..9b73d53e0639 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -70,7 +70,7 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.ShellSharedConstants;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -109,7 +109,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ private DesktopRepository mDesktopRepository;
@Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
@@ -146,7 +146,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
mDisplayInsetsController, mMainExecutor));
mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
- Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
+ Optional.of(mDesktopRepository), mTaskStackTransitionObserver,
mMainExecutor);
mRecentTasksController = spy(mRecentTasksControllerReal);
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
@@ -305,8 +305,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
setRawList(t1, t2, t3, t4);
- when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -344,8 +344,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, pair1Bounds);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -384,8 +384,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
setRawList(t1, t2, t3, t4);
- when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -412,10 +412,10 @@ public class RecentTasksControllerTest extends ShellTestCase {
ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
setRawList(t1, t2, t3, t4, t5);
- when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true);
- when(mDesktopModeTaskRepository.isMinimizedTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
+ when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true);
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -452,8 +452,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450);
setRawList(t1, t2);
- when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(2)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(2)).thenReturn(true);
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index 769acf7fdfde..0effc3e3d6b8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -48,7 +48,7 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -82,7 +82,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ private DesktopRepository mDesktopRepository;
@Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
@@ -120,7 +120,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
mDisplayInsetsController, mMainExecutor));
mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
- Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
+ Optional.of(mDesktopRepository), mTaskStackTransitionObserver,
mMainExecutor);
mRecentTasksController = spy(mRecentTasksControllerReal);
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 5ae4ca839d61..83bd15b79186 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -87,7 +87,7 @@ import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository
+import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTasksLimiter
@@ -161,7 +161,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer
@Mock private lateinit var mockDisplayController: DisplayController
@Mock private lateinit var mockSplitScreenController: SplitScreenController
- @Mock private lateinit var mockDesktopRepository: DesktopModeTaskRepository
+ @Mock private lateinit var mockDesktopRepository: DesktopRepository
@Mock private lateinit var mockDisplayLayout: DisplayLayout
@Mock private lateinit var displayInsetsController: DisplayInsetsController
@Mock private lateinit var mockSyncQueue: SyncTransactionQueue
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index ed752fdada77..35be80e87ecd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -106,7 +106,7 @@ import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.CaptionState;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -163,7 +163,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Mock
private ShellTaskOrganizer mMockShellTaskOrganizer;
@Mock
- private DesktopModeTaskRepository mMockDesktopRepository;
+ private DesktopRepository mMockDesktopRepository;
@Mock
private Choreographer mMockChoreographer;
@Mock
@@ -1046,6 +1046,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
any(),
openInBrowserCaptor.capture(),
any(),
+ any(),
any()
);
openInBrowserCaptor.getValue().invoke(new Intent(Intent.ACTION_MAIN, TEST_URI1));
@@ -1074,6 +1075,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
any(),
openInBrowserCaptor.capture(),
any(),
+ any(),
any()
);
@@ -1124,6 +1126,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
any(),
any(),
any(),
+ any(),
closeClickListener.capture(),
any()
);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index cabd472ec263..1820133a4795 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -240,7 +240,7 @@ class HandleMenuTest : ShellTestCase() {
null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
captionX = captionX
)
- handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock())
+ handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock())
return handleMenu
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 8b9ec389e3fd..739c7d6bcfca 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1651,6 +1651,9 @@
<!-- Name of the usb audio device mic. [CHAR LIMIT=50] -->
<string name="media_transfer_usb_device_mic_name">USB microphone</string>
+ <!-- Name of the bluetooth audio device mic. [CHAR LIMIT=50] -->
+ <string name="media_transfer_bt_device_mic_name">BT microphone</string>
+
<!-- Label for Wifi hotspot switch on. Toggles hotspot on [CHAR LIMIT=30] -->
<string name="wifi_hotspot_switch_on_text">On</string>
<!-- Label for Wifi hotspot switch off. Toggles hotspot off [CHAR LIMIT=30] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
index e44a13491758..1d17b004fb6a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
@@ -15,6 +15,7 @@
*/
package com.android.settingslib.media;
+import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
import static android.media.AudioDeviceInfo.TYPE_USB_ACCESSORY;
import static android.media.AudioDeviceInfo.TYPE_USB_DEVICE;
@@ -90,7 +91,8 @@ public class InputMediaDevice extends MediaDevice {
TYPE_WIRED_HEADSET,
TYPE_USB_DEVICE,
TYPE_USB_HEADSET,
- TYPE_USB_ACCESSORY ->
+ TYPE_USB_ACCESSORY,
+ TYPE_BLUETOOTH_SCO ->
true;
default -> false;
};
@@ -103,6 +105,8 @@ public class InputMediaDevice extends MediaDevice {
R.string.media_transfer_wired_device_mic_name);
case TYPE_USB_DEVICE, TYPE_USB_HEADSET, TYPE_USB_ACCESSORY -> mContext.getString(
R.string.media_transfer_usb_device_mic_name);
+ case TYPE_BLUETOOTH_SCO -> mContext.getString(
+ R.string.media_transfer_bt_device_mic_name);
default -> mContext.getString(R.string.media_transfer_this_device_name_desktop);
};
return name.toString();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
index 2f0aa1cf0cb9..30e4637f59ff 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
@@ -37,6 +37,7 @@ public class InputMediaDeviceTest {
private final int BUILTIN_MIC_ID = 1;
private final int WIRED_HEADSET_ID = 2;
private final int USB_HEADSET_ID = 3;
+ private final int BT_HEADSET_ID = 4;
private final int MAX_VOLUME = 1;
private final int CURRENT_VOLUME = 0;
private final boolean IS_VOLUME_FIXED = true;
@@ -108,4 +109,19 @@ public class InputMediaDeviceTest {
assertThat(usbMediaDevice.getName())
.isEqualTo(mContext.getString(R.string.media_transfer_usb_device_mic_name));
}
+
+ @Test
+ public void getName_returnCorrectName_btHeadset() {
+ InputMediaDevice btMediaDevice =
+ InputMediaDevice.create(
+ mContext,
+ String.valueOf(BT_HEADSET_ID),
+ AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+ MAX_VOLUME,
+ CURRENT_VOLUME,
+ IS_VOLUME_FIXED);
+ assertThat(btMediaDevice).isNotNull();
+ assertThat(btMediaDevice.getName())
+ .isEqualTo(mContext.getString(R.string.media_transfer_bt_device_mic_name));
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 0773bd701d5c..8f58e8cd1973 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -434,7 +434,7 @@ public class GlobalSettingsValidators {
VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_STATUS,
new InclusiveIntegerRangeValidator(
Global.Wearable.PHONE_SWITCHING_STATUS_NOT_STARTED,
- Global.Wearable.PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_SUCCESS));
+ Global.Wearable.PHONE_SWITCHING_STATUS_ACCOUNTS_MATCHED));
VALIDATORS.put(Global.Wearable.REDUCE_MOTION, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.RTL_SWIPE_TO_DISMISS_ENABLED_DEV, BOOLEAN_VALIDATOR);
VALIDATORS.put(
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index b491b5a146e1..d39b5645109d 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -223,7 +223,6 @@ public class SettingsBackupTest {
Settings.Global.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE,
Settings.Global.ENABLE_DISKSTATS_LOGGING,
Settings.Global.ENABLE_EPHEMERAL_FEATURE,
- Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED,
Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED,
Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index aaa527dead0a..c6238e848070 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1473,3 +1473,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "check_lockscreen_gone_transition"
+ namespace: "systemui"
+ description: "Run notification pipeline when the lockscreen is not in gone transition for avoiding janky frames during unlocking animation"
+ bug: "358301118"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index beba16229c12..ea5c29ef30aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -20,8 +20,6 @@ import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
-import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -31,12 +29,16 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
+import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.compose.animation.scene.ObservableTransitionState;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.communal.shared.model.CommunalScenes;
import com.android.systemui.dump.DumpManager;
@@ -67,9 +69,6 @@ import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.test.TestScope;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -79,6 +78,9 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.verification.VerificationMode;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.test.TestScope;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
@@ -517,6 +519,32 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
+ public void testNotLockscreenInGoneTransition_invalidationCalled() {
+ // GIVEN visual stability is being maintained b/c animation is playing
+ mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
+ mTestScope, new TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ 1f,
+ TransitionState.RUNNING), /* validateStep = */ false);
+ mTestScope.getTestScheduler().runCurrent();
+ assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
+
+ // WHEN the animation has stopped playing
+ mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
+ mTestScope, new TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ 1f,
+ TransitionState.FINISHED), /* validateStep = */ false);
+ mTestScope.getTestScheduler().runCurrent();
+
+ // invalidate is called, b/c we were previously suppressing the pipeline from running
+ verifyStabilityManagerWasInvalidated(times(1));
+ }
+
+ @Test
public void testNeverSuppressPipelineRunFromPanelCollapse_noInvalidationCalled() {
// GIVEN animation is playing
setPanelCollapsing(true);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt
deleted file mode 100644
index a310ef44cf35..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.statusbar.notification.row.data.repository.fakeNotificationRowRepository
-import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
-import com.android.systemui.statusbar.notification.row.shared.IconModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-@EnableFlags(RichOngoingNotificationFlag.FLAG_NAME)
-class EnRouteViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val repository = kosmos.fakeNotificationRowRepository
-
- private var contentModel: EnRouteContentModel?
- get() = repository.richOngoingContentModel.value as? EnRouteContentModel
- set(value) {
- repository.richOngoingContentModel.value = value
- }
-
- private lateinit var underTest: EnRouteViewModel
-
- @Before
- fun setup() {
- underTest = kosmos.getEnRouteViewModel(repository)
- }
-
- @Test
- fun viewModelShowsContent() =
- testScope.runTest {
- val title by collectLastValue(underTest.title)
- val text by collectLastValue(underTest.text)
- contentModel =
- exampleEnRouteContent(
- title = "Example EnRoute Title",
- text = "Example EnRoute Text",
- )
- assertThat(title).isEqualTo("Example EnRoute Title")
- assertThat(text).isEqualTo("Example EnRoute Text")
- }
-
- private fun exampleEnRouteContent(
- icon: IconModel = mock(),
- title: CharSequence = "example text",
- text: CharSequence = "example title",
- ) =
- EnRouteContentModel(
- smallIcon = icon,
- title = title,
- text = text,
- )
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt
deleted file mode 100644
index 61873ad294e3..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-import android.app.Notification
-import android.app.PendingIntent
-import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.statusbar.notification.row.data.repository.fakeNotificationRowRepository
-import com.android.systemui.statusbar.notification.row.shared.IconModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel.TimerState.Paused
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import java.time.Duration
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-@EnableFlags(RichOngoingNotificationFlag.FLAG_NAME)
-class TimerViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val repository = kosmos.fakeNotificationRowRepository
-
- private var contentModel: TimerContentModel?
- get() = repository.richOngoingContentModel.value as? TimerContentModel
- set(value) {
- repository.richOngoingContentModel.value = value
- }
-
- private lateinit var underTest: TimerViewModel
-
- @Before
- fun setup() {
- underTest = kosmos.getTimerViewModel(repository)
- }
-
- @Test
- fun labelShowsTheTimerName() =
- testScope.runTest {
- val label by collectLastValue(underTest.label)
- contentModel = pausedTimer(name = "Example Timer Name")
- assertThat(label).isEqualTo("Example Timer Name")
- }
-
- @Test
- fun pausedTimeRemainingFormatsWell() =
- testScope.runTest {
- val label by collectLastValue(underTest.pausedTime)
- contentModel = pausedTimer(timeRemaining = Duration.ofMinutes(3))
- assertThat(label).isEqualTo("3:00")
- contentModel = pausedTimer(timeRemaining = Duration.ofSeconds(119))
- assertThat(label).isEqualTo("1:59")
- contentModel = pausedTimer(timeRemaining = Duration.ofSeconds(121))
- assertThat(label).isEqualTo("2:01")
- contentModel = pausedTimer(timeRemaining = Duration.ofHours(1))
- assertThat(label).isEqualTo("1:00:00")
- contentModel = pausedTimer(timeRemaining = Duration.ofHours(24))
- assertThat(label).isEqualTo("24:00:00")
- }
-
- private fun pausedTimer(
- icon: IconModel = mock(),
- name: String = "example",
- timeRemaining: Duration = Duration.ofMinutes(3),
- resumeIntent: PendingIntent? = null,
- addMinuteAction: Notification.Action? = null,
- resetAction: Notification.Action? = null
- ) =
- TimerContentModel(
- icon = icon,
- name = name,
- state =
- Paused(
- timeRemaining = timeRemaining,
- resumeIntent = resumeIntent,
- addMinuteAction = addMinuteAction,
- resetAction = resetAction,
- )
- )
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
index e1be6b008903..d2688a8fc146 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
@@ -53,7 +53,7 @@ import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.wm.shell.desktopmode.DesktopMode
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
import com.android.wm.shell.onehanded.OneHanded
import com.android.wm.shell.onehanded.OneHandedEventCallback
import com.android.wm.shell.onehanded.OneHandedTransitionCallback
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml
new file mode 100644
index 000000000000..8a77d88a7e83
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2024, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** 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.
+*/
+-->
+
+<!-- This is the screen that shows the 9 circle unlock widget and instructs
+ the user how to unlock their device, or make an emergency call. This
+ is the landscape layout. -->
+<com.android.keyguard.KeyguardPatternView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/keyguard_pattern_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="2"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <include layout="@layout/keyguard_bouncer_message_area"/>
+
+ <com.android.systemui.bouncer.ui.BouncerMessageView
+ android:id="@+id/bouncer_message_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toTopOf="@+id/lockPatternView"
+ androidprv:layout_constraintTop_toTopOf="parent"
+ androidprv:layout_constraintVertical_chainStyle="packed" />
+
+ <include
+ android:id="@+id/keyguard_selector_fade_container"
+ layout="@layout/keyguard_eca"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toBottomOf="parent" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/pattern_container"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/pattern_top_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ androidprv:layout_constraintGuide_percent="0" />
+
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPatternView"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal"
+ androidprv:layout_constraintDimensionRatio="1.0"
+ androidprv:layout_constraintVertical_bias="1.0"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintTop_toBottomOf="@id/pattern_top_guideline"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.keyguard.KeyguardPatternView>
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_pin_view.xml
new file mode 100644
index 000000000000..4b8b63fe9396
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_pin_view.xml
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2024, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** 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.
+*/
+-->
+<!-- This is the SIM PIN view that allows the user to enter a SIM PIN to unlock the device. -->
+<com.android.keyguard.KeyguardSimPinView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/keyguard_sim_pin_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="2"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <include layout="@layout/keyguard_bouncer_message_area"/>
+
+ <ImageView
+ android:id="@+id/keyguard_sim"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_marginBottom="3dp"
+ android:src="@drawable/ic_lockscreen_sim"
+ androidprv:layout_constraintBottom_toTopOf="@+id/pin_entry_area"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ app:tint="@color/background_protected"/>
+
+ <include
+ android:id="@+id/keyguard_esim_area"
+ layout="@layout/keyguard_esim_area"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ androidprv:layout_constraintBottom_toTopOf="@+id/pin_entry_area"/>
+
+ <com.android.keyguard.AlphaOptimizedRelativeLayout
+ android:id="@+id/pin_entry_area"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container">
+
+ <com.android.keyguard.PasswordTextView
+ android:id="@+id/simPinEntry"
+ style="@style/Widget.TextView.Password"
+ android:layout_width="@dimen/keyguard_security_width"
+ android:layout_height="@dimen/keyguard_password_height"
+ android:layout_centerHorizontal="true"
+ android:layout_marginRight="72dp"
+ android:contentDescription="@string/keyguard_accessibility_sim_pin_area"
+ androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+ </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+ <include
+ android:id="@+id/keyguard_selector_fade_container"
+ layout="@layout/keyguard_eca"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toBottomOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/sim_pin_container"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <androidx.constraintlayout.helper.widget.Flow
+ android:id="@+id/flow1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal"
+ androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+ androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+ androidprv:flow_horizontalStyle="packed"
+ androidprv:flow_maxElementsWrap="3"
+ androidprv:flow_verticalBias="0.5"
+ androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:flow_verticalStyle="packed"
+ androidprv:flow_wrapMode="aligned"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ androidprv:layout_constraintTop_toTopOf="parent"
+ androidprv:layout_constraintBottom_toBottomOf="parent"/>
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/delete_button"
+ style="@style/NumPadKey.Delete"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key0"
+ android:contentDescription="@string/keyboardview_keycode_delete" />
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/key_enter"
+ style="@style/NumPadKey.Enter"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:contentDescription="@string/keyboardview_keycode_enter" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key2"
+ androidprv:digit="1"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key2"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key3"
+ androidprv:digit="2"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key3"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key4"
+ androidprv:digit="3"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key4"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key5"
+ androidprv:digit="4"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key5"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key6"
+ androidprv:digit="5"
+ androidprv:textView="@+id/simPinEntry" />
+
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key6"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key7"
+ androidprv:digit="6"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key7"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key8"
+ androidprv:digit="7"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key8"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key9"
+ androidprv:digit="8"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key9"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/delete_button"
+ androidprv:digit="9"
+ androidprv:textView="@+id/simPinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key0"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key_enter"
+ androidprv:digit="0"
+ androidprv:textView="@+id/simPinEntry" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.keyguard.KeyguardSimPinView>
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_puk_view.xml
new file mode 100644
index 000000000000..9012856c7bb4
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_puk_view.xml
@@ -0,0 +1,223 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2024, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** 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.
+*/
+-->
+<!-- This is the SIM PUK view that allows the user to recover their device by entering the
+ carrier-provided PUK code and entering a new SIM PIN for it. -->
+<com.android.keyguard.KeyguardSimPukView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/keyguard_sim_puk_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="2"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <include layout="@layout/keyguard_bouncer_message_area"/>
+
+ <ImageView
+ android:id="@+id/keyguard_sim"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_marginBottom="3dp"
+ android:src="@drawable/ic_lockscreen_sim"
+ androidprv:layout_constraintBottom_toTopOf="@+id/pin_entry_area"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ app:tint="@color/background_protected"/>
+
+ <include
+ android:id="@+id/keyguard_esim_area"
+ layout="@layout/keyguard_esim_area"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ androidprv:layout_constraintBottom_toTopOf="@+id/pin_entry_area"/>
+
+ <com.android.keyguard.AlphaOptimizedRelativeLayout
+ android:id="@+id/pin_entry_area"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container">
+
+ <com.android.keyguard.PasswordTextView
+ android:id="@+id/pukEntry"
+ style="@style/Widget.TextView.Password"
+ android:layout_width="@dimen/keyguard_security_width"
+ android:layout_height="@dimen/keyguard_password_height"
+ android:layout_centerHorizontal="true"
+ android:layout_marginRight="72dp"
+ android:contentDescription="@string/keyguard_accessibility_sim_pin_area"
+ androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+ </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+ <include
+ android:id="@+id/keyguard_selector_fade_container"
+ layout="@layout/keyguard_eca"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toBottomOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/sim_puk_container"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <androidx.constraintlayout.helper.widget.Flow
+ android:id="@+id/flow1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal"
+ androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+ androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+ androidprv:flow_horizontalStyle="packed"
+ androidprv:flow_maxElementsWrap="3"
+ androidprv:flow_verticalBias="0.5"
+ androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:flow_verticalStyle="packed"
+ androidprv:flow_wrapMode="aligned"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ androidprv:layout_constraintTop_toTopOf="parent"
+ androidprv:layout_constraintBottom_toBottomOf="parent"/>
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/delete_button"
+ style="@style/NumPadKey.Delete"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key0"
+ android:contentDescription="@string/keyboardview_keycode_delete" />
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/key_enter"
+ style="@style/NumPadKey.Enter"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:contentDescription="@string/keyboardview_keycode_enter" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key2"
+ androidprv:digit="1"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key2"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key3"
+ androidprv:digit="2"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key3"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key4"
+ androidprv:digit="3"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key4"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key5"
+ androidprv:digit="4"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key5"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key6"
+ androidprv:digit="5"
+ androidprv:textView="@+id/pukEntry" />
+
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key6"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key7"
+ androidprv:digit="6"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key7"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key8"
+ androidprv:digit="7"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key8"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key9"
+ androidprv:digit="8"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key9"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/delete_button"
+ androidprv:digit="9"
+ androidprv:textView="@+id/pukEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key0"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key_enter"
+ androidprv:digit="0"
+ androidprv:textView="@+id/pukEntry" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.keyguard.KeyguardSimPukView>
diff --git a/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml b/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml
deleted file mode 100644
index ca6d66a370bd..000000000000
--- a/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (C) 2014 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
- -->
-<com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@*android:id/status_bar_latest_event_content"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:clipChildren="false"
- android:tag="big"
- >
-
- <LinearLayout
- android:id="@*android:id/notification_action_list_margin_target"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@*android:dimen/notification_content_margin"
- android:orientation="vertical"
- >
-
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="top"
- >
-
- <include layout="@*android:layout/notification_template_header" />
-
- <LinearLayout
- android:id="@*android:id/notification_main_column"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="@*android:dimen/notification_content_margin_start"
- android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
- android:layout_marginTop="@*android:dimen/notification_content_margin_top"
- android:orientation="vertical"
- >
-
- <include layout="@*android:layout/notification_template_part_line1" />
-
- <include layout="@*android:layout/notification_template_text_multiline" />
-
- <include
- android:layout_width="match_parent"
- android:layout_height="@*android:dimen/notification_progress_bar_height"
- android:layout_marginTop="@*android:dimen/notification_progress_margin_top"
- layout="@*android:layout/notification_template_progress"
- />
- </LinearLayout>
-
- <include layout="@*android:layout/notification_template_right_icon" />
- </FrameLayout>
-
- <ViewStub
- android:layout="@*android:layout/notification_material_reply_text"
- android:id="@*android:id/notification_material_reply_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
-
- <include
- layout="@*android:layout/notification_template_smart_reply_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="@*android:dimen/notification_content_margin_start"
- android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
- android:layout_marginTop="@*android:dimen/notification_content_margin"
- />
-
- <include layout="@*android:layout/notification_material_action_list" />
- </LinearLayout>
-</com.android.systemui.statusbar.notification.row.ui.view.EnRouteView>
diff --git a/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml b/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml
deleted file mode 100644
index 3a679e3c16cb..000000000000
--- a/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml
+++ /dev/null
@@ -1,122 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ 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
- -->
-<com.android.systemui.statusbar.notification.row.ui.view.TimerView
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- xmlns:app="http://schemas.android.com/apk/res-auto">
-
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/topBaseline"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- app:layout_constraintGuide_begin="22sp"
- />
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- app:tint="@android:color/white"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/label"
- android:baseline="18dp"
- app:layout_constraintBaseline_toTopOf="@id/topBaseline"
- />
- <TextView
- android:id="@+id/label"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- app:layout_constraintStart_toEndOf="@id/icon"
- app:layout_constraintEnd_toStartOf="@id/chronoRemaining"
- android:singleLine="true"
- tools:text="15s Timer"
- app:layout_constraintBaseline_toTopOf="@id/topBaseline"
- android:paddingEnd="4dp"
- />
- <Chronometer
- android:id="@+id/chronoRemaining"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:textSize="20sp"
- android:gravity="end"
- tools:text="0:12"
- app:layout_constraintBaseline_toTopOf="@id/topBaseline"
- app:layout_constraintEnd_toStartOf="@id/pausedTimeRemaining"
- app:layout_constraintStart_toEndOf="@id/label"
- android:countDown="true"
- android:paddingEnd="4dp"
- />
- <TextView
- android:id="@+id/pausedTimeRemaining"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:textSize="20sp"
- android:gravity="end"
- tools:text="0:12"
- app:layout_constraintBaseline_toTopOf="@id/topBaseline"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toEndOf="@id/chronoRemaining"
- android:paddingEnd="4dp"
- />
-
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/bottomOfTop"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- app:barrierDirection="bottom"
- app:constraint_referenced_ids="icon,label,chronoRemaining,pausedTimeRemaining"
- />
-
- <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
- style="@*android:style/NotificationEmphasizedAction"
- android:id="@+id/mainButton"
- android:layout_width="124dp"
- android:layout_height="wrap_content"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/altButton"
- app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
- app:layout_constraintHorizontal_chainStyle="spread"
- android:paddingEnd="4dp"
- />
-
- <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
- style="@*android:style/NotificationEmphasizedAction"
- android:id="@+id/altButton"
- android:layout_width="124dp"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
- app:layout_constraintStart_toEndOf="@id/mainButton"
- app:layout_constraintEnd_toEndOf="@id/resetButton"
- android:paddingEnd="4dp"
- />
-
- <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
- style="@*android:style/NotificationEmphasizedAction"
- android:id="@+id/resetButton"
- android:layout_width="124dp"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
- app:layout_constraintStart_toEndOf="@id/altButton"
- app:layout_constraintEnd_toEndOf="parent"
- android:paddingEnd="4dp"
- />
-</com.android.systemui.statusbar.notification.row.ui.view.TimerView> \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index ea19020d84d4..f0f476e65e2f 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -25,6 +25,7 @@ import android.view.View
import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.core.view.updateMargins
+import com.android.systemui.Flags
import com.android.systemui.compose.ComposeInitializer
import com.android.systemui.res.R
@@ -103,6 +104,8 @@ open class WindowRootView(
private fun applyMargins() {
val count = childCount
+ val hasFlagsEnabled = Flags.checkLockscreenGoneTransition()
+ var hasChildMarginUpdated = false
for (i in 0 until count) {
val child = getChildAt(i)
if (child.layoutParams is LayoutParams) {
@@ -113,10 +116,17 @@ open class WindowRootView(
layoutParams.leftMargin != leftInset)
) {
layoutParams.updateMargins(left = leftInset, right = rightInset)
- child.requestLayout()
+ hasChildMarginUpdated = true
+ if (!hasFlagsEnabled) {
+ child.requestLayout()
+ }
}
}
}
+ if (hasFlagsEnabled && hasChildMarginUpdated) {
+ // Request layout at once after all children's margins has updated
+ requestLayout()
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
index a4906c12b487..91efa0a283a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
@@ -41,7 +41,7 @@ import kotlin.math.sign
class ScreenshotAnimationController(
private val view: ScreenshotShelfView,
- private val viewModel: ScreenshotViewModel
+ private val viewModel: ScreenshotViewModel,
) {
private var animator: Animator? = null
private val screenshotPreview = view.requireViewById<ImageView>(R.id.screenshot_preview)
@@ -56,7 +56,7 @@ class ScreenshotAnimationController(
listOf<View>(
view.requireViewById(R.id.screenshot_preview_border),
view.requireViewById(R.id.screenshot_badge),
- view.requireViewById(R.id.screenshot_dismiss_button)
+ view.requireViewById(R.id.screenshot_dismiss_button),
)
private val fadeUI =
listOf<View>(
@@ -70,9 +70,11 @@ class ScreenshotAnimationController(
fun getEntranceAnimation(
bounds: Rect,
showFlash: Boolean,
- onRevealMilestone: () -> Unit
+ onRevealMilestone: () -> Unit,
): Animator {
val entranceAnimation = AnimatorSet()
+ view.alpha = 1f
+ view.translationX = 0f
val previewAnimator = getPreviewAnimator(bounds)
@@ -142,7 +144,7 @@ class ScreenshotAnimationController(
fun runLongScreenshotTransition(
destRect: Rect,
longScreenshot: ScrollCaptureController.LongScreenshot,
- onTransitionEnd: Runnable
+ onTransitionEnd: Runnable,
): Animator {
val animSet = AnimatorSet()
@@ -165,7 +167,7 @@ class ScreenshotAnimationController(
matrix.setScale(currentScale, currentScale)
matrix.postTranslate(
longScreenshot.left * currentScale,
- longScreenshot.top * currentScale
+ longScreenshot.top * currentScale,
)
scrollTransitionPreview.setImageMatrix(matrix)
val destinationScale: Float = destRect.width() / scrollTransitionPreview.width.toFloat()
@@ -315,7 +317,7 @@ class ScreenshotAnimationController(
}
private fun getAdjustedVelocity(requestedVelocity: Float?): Float {
- return if (requestedVelocity == null) {
+ return if (requestedVelocity == null || abs(requestedVelocity) < .005f) {
val isLTR = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
// dismiss to the left in LTR locales, to the right in RTL
if (isLTR) -MINIMUM_VELOCITY else MINIMUM_VELOCITY
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt
index 61d448960f98..ecb45e1f9373 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt
@@ -25,7 +25,7 @@ import kotlin.math.abs
class SwipeGestureListener(
private val view: View,
private val onDismiss: (Float?) -> Unit,
- private val onCancel: () -> Unit
+ private val onCancel: () -> Unit,
) {
private val velocityTracker = VelocityTracker.obtain()
private val displayMetrics = view.resources.displayMetrics
@@ -54,9 +54,9 @@ class SwipeGestureListener(
onDismiss.invoke(xVelocity)
return true
} else {
- velocityTracker.clear()
onCancel.invoke()
}
+ velocityTracker.clear()
}
MotionEvent.ACTION_MOVE -> {
velocityTracker.addMovement(ev)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index e47952fd6c4a..a79b78f8d5f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -58,6 +58,7 @@ import androidx.annotation.WorkerThread;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -813,11 +814,17 @@ public class NotificationLockscreenUserManagerImpl implements
private void notifyNotificationStateChanged() {
if (!Looper.getMainLooper().isCurrentThread()) {
- mMainExecutor.execute(() -> {
+ if (Flags.checkLockscreenGoneTransition()) {
for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
- listener.onNotificationStateChanged();
+ mMainExecutor.execute(listener::onNotificationStateChanged);
}
- });
+ } else {
+ mMainExecutor.execute(() -> {
+ for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
+ listener.onNotificationStateChanged();
+ }
+ });
+ }
} else {
for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
listener.onNotificationStateChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index cb133ecadab2..e74ed8d27ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -68,11 +68,9 @@ import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository;
import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel;
import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel;
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel;
import com.android.systemui.statusbar.notification.stack.PriorityBucket;
import com.android.systemui.util.ListenerSet;
@@ -99,7 +97,7 @@ import java.util.Objects;
* At the moment, there are many things here that shouldn't be and vice-versa. Hopefully we can
* clean this up in the future.
*/
-public final class NotificationEntry extends ListEntry implements NotificationRowRepository {
+public final class NotificationEntry extends ListEntry {
private final String mKey;
private StatusBarNotification mSbn;
@@ -161,8 +159,6 @@ public final class NotificationEntry extends ListEntry implements NotificationRo
StateFlowKt.MutableStateFlow(null);
private final MutableStateFlow<CharSequence> mHeadsUpStatusBarTextPublic =
StateFlowKt.MutableStateFlow(null);
- private final MutableStateFlow<RichOngoingContentModel> mRichOngoingContentModel =
- StateFlowKt.MutableStateFlow(null);
// indicates when this entry's view was first attached to a window
// this value will reset when the view is completely removed from the shade (ie: filtered out)
@@ -969,12 +965,6 @@ public final class NotificationEntry extends ListEntry implements NotificationRo
return mHeadsUpStatusBarTextPublic;
}
- /** Gets the current RON content model, which may be null */
- @NonNull
- public StateFlow<RichOngoingContentModel> getRichOngoingContentModel() {
- return mRichOngoingContentModel;
- }
-
/**
* Sets the text to be displayed on the StatusBar, when this notification is the top pinned
* heads up, and its content is sensitive right now.
@@ -1069,7 +1059,6 @@ public final class NotificationEntry extends ListEntry implements NotificationRo
HeadsUpStatusBarModel headsUpStatusBarModel = contentModel.getHeadsUpStatusBarModel();
this.mHeadsUpStatusBarText.setValue(headsUpStatusBarModel.getPrivateText());
this.mHeadsUpStatusBarTextPublic.setValue(headsUpStatusBarModel.getPublicText());
- this.mRichOngoingContentModel.setValue(contentModel.getRichOngoingContentModel());
}
/** Information about a suggestion that is being edited. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 41419f31eb7a..8660cd117493 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -22,15 +22,18 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
@@ -85,6 +88,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
private boolean mNotifPanelLaunchingActivity;
private boolean mCommunalShowing = false;
private boolean mLockscreenShowing = false;
+ private boolean mLockscreenInGoneTransition = false;
private boolean mPipelineRunAllowed;
private boolean mReorderingAllowed;
@@ -158,6 +162,13 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
KeyguardState.LOCKSCREEN),
this::onLockscreenKeyguardStateTransitionValueChanged);
}
+ if (Flags.checkLockscreenGoneTransition()) {
+ mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.isInTransition(
+ Edge.create(KeyguardState.LOCKSCREEN, Scenes.Gone),
+ Edge.create(KeyguardState.LOCKSCREEN, KeyguardState.GONE)),
+ this::onLockscreenInGoneTransitionChanged);
+ }
+
pipeline.setVisualStabilityManager(mNotifStabilityManager);
}
@@ -239,7 +250,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
private void updateAllowedStates(String field, boolean value) {
boolean wasPipelineRunAllowed = mPipelineRunAllowed;
boolean wasReorderingAllowed = mReorderingAllowed;
- mPipelineRunAllowed = !isPanelCollapsingOrLaunchingActivity();
+ // No need to run notification pipeline when the lockscreen is in fading animation.
+ mPipelineRunAllowed = !(isPanelCollapsingOrLaunchingActivity()
+ || (Flags.checkLockscreenGoneTransition() && mLockscreenInGoneTransition));
mReorderingAllowed = isReorderingAllowed();
if (wasPipelineRunAllowed != mPipelineRunAllowed
|| wasReorderingAllowed != mReorderingAllowed) {
@@ -330,7 +343,6 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
updateAllowedStates("fullyDozed", fullyDozed);
}
};
-
final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedGoingToSleep() {
@@ -353,6 +365,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
pw.println("pipelineRunAllowed: " + mPipelineRunAllowed);
pw.println(" notifPanelCollapsing: " + mNotifPanelCollapsing);
pw.println(" launchingNotifActivity: " + mNotifPanelLaunchingActivity);
+ if (Flags.checkLockscreenGoneTransition()) {
+ pw.println(" lockscreenInGoneTransition: " + mLockscreenInGoneTransition);
+ }
pw.println("reorderingAllowed: " + mReorderingAllowed);
pw.println(" sleepy: " + mSleepy);
pw.println(" fullyDozed: " + mFullyDozed);
@@ -401,4 +416,15 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
mLockscreenShowing = isShowing;
updateAllowedStates("lockscreenShowing", isShowing);
}
+
+ private void onLockscreenInGoneTransitionChanged(boolean inGoneTransition) {
+ if (!Flags.checkLockscreenGoneTransition()) {
+ return;
+ }
+ if (inGoneTransition == mLockscreenInGoneTransition) {
+ return;
+ }
+ mLockscreenInGoneTransition = inGoneTransition;
+ updateAllowedStates("lockscreenInGoneTransition", mLockscreenInGoneTransition);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 48c974a33f12..9166e7e9bed2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -76,8 +76,6 @@ import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
import com.android.systemui.util.Compile;
import com.android.systemui.util.DumpUtilsKt;
-import kotlinx.coroutines.DisposableHandle;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -116,10 +114,6 @@ public class NotificationContentView extends FrameLayout implements Notification
@VisibleForTesting
protected HybridNotificationView mSingleLineView;
- @Nullable public DisposableHandle mContractedBinderHandle;
- @Nullable public DisposableHandle mExpandedBinderHandle;
- @Nullable public DisposableHandle mHeadsUpBinderHandle;
-
private RemoteInputView mExpandedRemoteInput;
private RemoteInputView mHeadsUpRemoteInput;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index c342bcd2706b..b166defb96a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -46,9 +46,6 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP
@@ -71,7 +68,6 @@ import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedac
import com.android.systemui.statusbar.notification.row.shared.NewRemoteViews
import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
@@ -95,8 +91,6 @@ constructor(
private val remoteViewCache: NotifRemoteViewCache,
private val remoteInputManager: NotificationRemoteInputManager,
private val conversationProcessor: ConversationNotificationProcessor,
- private val ronExtractor: RichOngoingNotificationContentExtractor,
- private val ronInflater: RichOngoingNotificationViewInflater,
@NotifInflation private val inflationExecutor: Executor,
private val smartReplyStateInflater: SmartReplyStateInflater,
private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
@@ -144,8 +138,6 @@ constructor(
remoteViewCache,
entry,
conversationProcessor,
- ronExtractor,
- ronInflater,
row,
bindParams.isMinimized,
bindParams.usesIncreasedHeight,
@@ -190,7 +182,6 @@ constructor(
notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
headsUpStyleProvider = headsUpStyleProvider,
conversationProcessor = conversationProcessor,
- ronExtractor = ronExtractor,
logger = logger,
)
inflateSmartReplyViews(
@@ -282,22 +273,16 @@ constructor(
when (inflateFlag) {
FLAG_CONTENT_VIEW_CONTRACTED ->
row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_CONTRACTED) {
- row.privateLayout.mContractedBinderHandle?.dispose()
- row.privateLayout.mContractedBinderHandle = null
row.privateLayout.setContractedChild(null)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
}
FLAG_CONTENT_VIEW_EXPANDED ->
row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_EXPANDED) {
- row.privateLayout.mExpandedBinderHandle?.dispose()
- row.privateLayout.mExpandedBinderHandle = null
row.privateLayout.setExpandedChild(null)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
}
FLAG_CONTENT_VIEW_HEADS_UP ->
row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_HEADSUP) {
- row.privateLayout.mHeadsUpBinderHandle?.dispose()
- row.privateLayout.mHeadsUpBinderHandle = null
row.privateLayout.setHeadsUpChild(null)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
row.privateLayout.setHeadsUpInflatedSmartReplies(null)
@@ -378,8 +363,6 @@ constructor(
private val remoteViewCache: NotifRemoteViewCache,
private val entry: NotificationEntry,
private val conversationProcessor: ConversationNotificationProcessor,
- private val ronExtractor: RichOngoingNotificationContentExtractor,
- private val ronInflater: RichOngoingNotificationViewInflater,
private val row: ExpandableNotificationRow,
private val isMinimized: Boolean,
private val usesIncreasedHeight: Boolean,
@@ -459,7 +442,6 @@ constructor(
notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
headsUpStyleProvider = headsUpStyleProvider,
conversationProcessor = conversationProcessor,
- ronExtractor = ronExtractor,
logger = logger
)
logger.logAsyncTaskProgress(
@@ -506,90 +488,6 @@ constructor(
}
}
- val richOngoingContentModel = inflationProgress.contentModel.richOngoingContentModel
-
- if (
- richOngoingContentModel != null &&
- reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0
- ) {
- logger.logAsyncTaskProgress(entry, "inflating RON view")
- val inflateContractedView = reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0
- val inflateExpandedView = reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0
- val inflateHeadsUpView = reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0
-
- inflationProgress.contractedRichOngoingNotificationViewHolder =
- if (inflateContractedView) {
- ronInflater.inflateView(
- contentModel = richOngoingContentModel,
- existingView = row.privateLayout.contractedChild,
- entry = entry,
- systemUiContext = context,
- parentView = row.privateLayout,
- viewType = RichOngoingNotificationViewType.Contracted
- )
- } else {
- if (
- ronInflater.canKeepView(
- contentModel = richOngoingContentModel,
- existingView = row.privateLayout.contractedChild,
- viewType = RichOngoingNotificationViewType.Contracted
- )
- ) {
- KeepExistingView
- } else {
- NullContentView
- }
- }
-
- inflationProgress.expandedRichOngoingNotificationViewHolder =
- if (inflateExpandedView) {
- ronInflater.inflateView(
- contentModel = richOngoingContentModel,
- existingView = row.privateLayout.expandedChild,
- entry = entry,
- systemUiContext = context,
- parentView = row.privateLayout,
- viewType = RichOngoingNotificationViewType.Expanded
- )
- } else {
- if (
- ronInflater.canKeepView(
- contentModel = richOngoingContentModel,
- existingView = row.privateLayout.expandedChild,
- viewType = RichOngoingNotificationViewType.Expanded
- )
- ) {
- KeepExistingView
- } else {
- NullContentView
- }
- }
-
- inflationProgress.headsUpRichOngoingNotificationViewHolder =
- if (inflateHeadsUpView) {
- ronInflater.inflateView(
- contentModel = richOngoingContentModel,
- existingView = row.privateLayout.headsUpChild,
- entry = entry,
- systemUiContext = context,
- parentView = row.privateLayout,
- viewType = RichOngoingNotificationViewType.HeadsUp
- )
- } else {
- if (
- ronInflater.canKeepView(
- contentModel = richOngoingContentModel,
- existingView = row.privateLayout.headsUpChild,
- viewType = RichOngoingNotificationViewType.HeadsUp
- )
- ) {
- KeepExistingView
- } else {
- NullContentView
- }
- }
- }
-
logger.logAsyncTaskProgress(entry, "getting row image resolver (on wrong thread!)")
val imageResolver = row.imageResolver
// wait for image resolver to finish preloading
@@ -695,9 +593,6 @@ constructor(
var inflatedSmartReplyState: InflatedSmartReplyState? = null
var expandedInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
var headsUpInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
- var contractedRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
- var expandedRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
- var headsUpRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
// Inflated SingleLineView that lacks the UI State
var inflatedSingleLineView: HybridNotificationView? = null
@@ -734,7 +629,6 @@ constructor(
val inflateHeadsUp =
(reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0 &&
result.remoteViews.headsUp != null)
-
if (inflateContracted || inflateExpanded || inflateHeadsUp) {
logger.logAsyncTaskProgress(entry, "inflating contracted smart reply state")
result.inflatedSmartReplyState = inflater.inflateSmartReplyState(entry)
@@ -776,7 +670,6 @@ constructor(
notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
headsUpStyleProvider: HeadsUpStyleProvider,
conversationProcessor: ConversationNotificationProcessor,
- ronExtractor: RichOngoingNotificationContentExtractor,
logger: NotificationRowContentBinderLogger
): InflationProgress {
// process conversations and extract the messaging style
@@ -785,24 +678,9 @@ constructor(
conversationProcessor.processNotification(entry, builder, logger)
} else null
- val richOngoingContentModel =
- if (reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0) {
- ronExtractor.extractContentModel(
- entry = entry,
- builder = builder,
- systemUIContext = systemUIContext,
- packageContext = packageContext
- )
- } else {
- // if we're not re-inflating any RON views, make sure the model doesn't change
- entry.richOngoingContentModel.value
- }
-
- val remoteViewsFlags = getRemoteViewsFlags(reInflateFlags, richOngoingContentModel)
-
val remoteViews =
createRemoteViews(
- reInflateFlags = remoteViewsFlags,
+ reInflateFlags = reInflateFlags,
builder = builder,
isMinimized = isMinimized,
usesIncreasedHeight = usesIncreasedHeight,
@@ -850,7 +728,6 @@ constructor(
headsUpStatusBarModel = headsUpStatusBarModel,
singleLineViewModel = singleLineViewModel,
publicSingleLineViewModel = publicSingleLineViewModel,
- richOngoingContentModel = richOngoingContentModel,
)
return InflationProgress(
@@ -1506,31 +1383,11 @@ constructor(
}
logger.logAsyncTaskProgress(entry, "finishing")
- // before updating the content model, stop existing binding if necessary
- if (result.contractedRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
- row.privateLayout.mContractedBinderHandle?.dispose()
- row.privateLayout.mContractedBinderHandle = null
- }
-
- if (result.expandedRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
- row.privateLayout.mExpandedBinderHandle?.dispose()
- row.privateLayout.mExpandedBinderHandle = null
- }
-
- if (result.headsUpRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
- row.privateLayout.mHeadsUpBinderHandle?.dispose()
- row.privateLayout.mHeadsUpBinderHandle = null
- }
-
- // set the content model after disposal and before setting new rich ongoing view
entry.setContentModel(result.contentModel)
result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
- // set normal remote views (skipping rich ongoing states when that model exists)
- val remoteViewsFlags =
- getRemoteViewsFlags(reInflateFlags, result.contentModel.richOngoingContentModel)
setContentViewsFromRemoteViews(
- remoteViewsFlags,
+ reInflateFlags,
entry,
remoteViewCache,
result,
@@ -1538,7 +1395,6 @@ constructor(
isMinimized,
)
- // set single line view
if (
AsyncHybridViewInflation.isEnabled &&
reInflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE != 0
@@ -1563,55 +1419,6 @@ constructor(
}
}
- val hasRichOngoingViewHolder =
- result.contractedRichOngoingNotificationViewHolder != null ||
- result.expandedRichOngoingNotificationViewHolder != null ||
- result.headsUpRichOngoingNotificationViewHolder != null
-
- if (hasRichOngoingViewHolder) {
- // after updating the content model, set the view, then start the new binder
- result.contractedRichOngoingNotificationViewHolder?.let { contractedViewHolder ->
- if (contractedViewHolder is InflatedContentViewHolder) {
- row.privateLayout.contractedChild = contractedViewHolder.view
- row.privateLayout.mContractedBinderHandle =
- contractedViewHolder.binder.setupContentViewBinder()
- } else if (contractedViewHolder == NullContentView) {
- row.privateLayout.contractedChild = null
- }
- }
-
- result.expandedRichOngoingNotificationViewHolder?.let { expandedViewHolder ->
- if (expandedViewHolder is InflatedContentViewHolder) {
- row.privateLayout.expandedChild = expandedViewHolder.view
- row.privateLayout.mExpandedBinderHandle =
- expandedViewHolder.binder.setupContentViewBinder()
- } else if (expandedViewHolder == NullContentView) {
- row.privateLayout.expandedChild = null
- }
- }
-
- result.headsUpRichOngoingNotificationViewHolder?.let { headsUpViewHolder ->
- if (headsUpViewHolder is InflatedContentViewHolder) {
- row.privateLayout.headsUpChild = headsUpViewHolder.view
- row.privateLayout.mHeadsUpBinderHandle =
- headsUpViewHolder.binder.setupContentViewBinder()
- } else if (headsUpViewHolder == NullContentView) {
- row.privateLayout.headsUpChild = null
- }
- }
-
- // clean remoteViewCache when we don't keep existing views.
- remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
- remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
- remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
-
- // Since RONs don't support smart reply, remove them from HUNs and Expanded.
- row.privateLayout.setExpandedInflatedSmartReplies(null)
- row.privateLayout.setHeadsUpInflatedSmartReplies(null)
-
- row.setExpandable(row.privateLayout.expandedChild != null)
- }
-
Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
endListener?.onAsyncInflationFinished(entry)
return true
@@ -1775,21 +1582,6 @@ constructor(
!oldView.hasFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED)
}
- @InflationFlag
- private fun getRemoteViewsFlags(
- @InflationFlag reInflateFlags: Int,
- richOngoingContentModel: RichOngoingContentModel?
- ): Int =
- if (richOngoingContentModel != null) {
- reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING.inv()
- } else {
- reInflateFlags
- }
-
- @InflationFlag
- private const val CONTENT_VIEWS_TO_CREATE_RICH_ONGOING =
- FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
-
private const val ASYNC_TASK_TRACE_METHOD =
"NotificationRowContentBinderImpl.AsyncInflationTask"
private const val APPLY_TRACE_METHOD = "NotificationRowContentBinderImpl#apply"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index c630c4d43fba..84f2f6670839 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -18,8 +18,6 @@ package com.android.systemui.statusbar.notification.row;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag;
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.RichOngoingViewModelComponent;
import dagger.Binds;
import dagger.Module;
@@ -30,7 +28,7 @@ import javax.inject.Provider;
/**
* Dagger Module containing notification row and view inflation implementations.
*/
-@Module(subcomponents = {RichOngoingViewModelComponent.class})
+@Module
public abstract class NotificationRowModule {
/**
@@ -49,25 +47,6 @@ public abstract class NotificationRowModule {
}
}
- /** Provides ron content model extractor. */
- @Provides
- @SysUISingleton
- public static RichOngoingNotificationContentExtractor provideRonContentExtractor(
- Provider<RichOngoingNotificationContentExtractorImpl> realImpl
- ) {
- if (RichOngoingNotificationFlag.isEnabled()) {
- return realImpl.get();
- } else {
- return new NoOpRichOngoingNotificationContentExtractor();
- }
- }
-
- /** Provides ron view inflater. */
- @Binds
- @SysUISingleton
- public abstract RichOngoingNotificationViewInflater provideRonViewInflater(
- RichOngoingNotificationViewInflaterImpl impl);
-
/**
* Provides notification remote view cache instance.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
deleted file mode 100644
index ec5ebc3651ee..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row
-
-import android.app.Notification
-import android.app.PendingIntent
-import android.content.Context
-import android.util.Log
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
-import com.android.systemui.statusbar.notification.row.shared.IconModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
-import java.time.Duration
-import java.time.LocalDate
-import java.time.LocalDateTime
-import java.time.LocalTime
-import java.time.ZoneId
-import javax.inject.Inject
-
-/**
- * Interface which provides a [RichOngoingContentModel] for a given [Notification] when one is
- * applicable to the given style.
- */
-interface RichOngoingNotificationContentExtractor {
- fun extractContentModel(
- entry: NotificationEntry,
- builder: Notification.Builder,
- systemUIContext: Context,
- packageContext: Context,
- ): RichOngoingContentModel?
-}
-
-class NoOpRichOngoingNotificationContentExtractor : RichOngoingNotificationContentExtractor {
- override fun extractContentModel(
- entry: NotificationEntry,
- builder: Notification.Builder,
- systemUIContext: Context,
- packageContext: Context,
- ): RichOngoingContentModel? = null
-}
-
-@SysUISingleton
-class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
- RichOngoingNotificationContentExtractor {
-
- init {
- /* check if */ RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()
- }
-
- override fun extractContentModel(
- entry: NotificationEntry,
- builder: Notification.Builder,
- systemUIContext: Context,
- packageContext: Context,
- ): RichOngoingContentModel? {
- val sbn = entry.sbn
- val notification = sbn.notification
- val icon = IconModel(notification.smallIcon)
-
- try {
- return if (sbn.packageName == "com.google.android.deskclock") {
- when (notification.channelId) {
- "Timers v2" -> {
- parseTimerNotification(notification, icon)
- }
- "Stopwatch v2" -> {
- Log.i("RONs", "Can't process stopwatch yet")
- null
- }
- else -> {
- Log.i("RONs", "Can't process channel '${notification.channelId}'")
- null
- }
- }
- } else if (builder.style is Notification.ProgressStyle) {
- parseEnRouteNotification(notification, icon)
- } else null
- } catch (e: Exception) {
- Log.e("RONs", "Error parsing RON", e)
- return null
- }
- }
-
- /**
- * FOR PROTOTYPING ONLY: create a RON TimerContentModel using the time information available
- * inside the sortKey of the clock app's timer notifications.
- */
- private fun parseTimerNotification(
- notification: Notification,
- icon: IconModel,
- ): TimerContentModel {
- // sortKey=1 0|↺7|RUNNING|▶16:21:58.523|Σ0:05:00|Δ0:00:03|⏳0:04:57
- // sortKey=1 0|↺7|PAUSED|Σ0:05:00|Δ0:04:54|⏳0:00:06
- // sortKey=1 1|↺7|RUNNING|▶16:30:28.433|Σ0:04:05|Δ0:00:06|⏳0:03:59
- // sortKey=1 0|↺7|RUNNING|▶16:36:18.350|Σ0:05:00|Δ0:01:42|⏳0:03:18
- // sortKey=1 2|↺7|RUNNING|▶16:38:37.816|Σ0:02:00|Δ0:01:09|⏳0:00:51
- // ▶ = "current" time (when updated)
- // Σ = total time
- // Δ = time elapsed
- // ⏳ = time remaining
- val sortKey = notification.sortKey
- val (_, _, state, extra) = sortKey.split("|", limit = 4)
- return when (state) {
- "PAUSED" -> {
- val (total, _, remaining) = extra.split("|")
- val timeRemaining = parseTimeDelta(remaining)
- TimerContentModel(
- icon = icon,
- // TODO: b/352142761 - define and use a string resource rather than " Timer".
- // (The UX isn't final so using " Timer" for now).
- name = total.replace("Σ", "") + " Timer",
- state =
- TimerContentModel.TimerState.Paused(
- timeRemaining = timeRemaining,
- resumeIntent = notification.findStartIntent(),
- addMinuteAction = notification.findAddMinuteAction(),
- resetAction = notification.findResetAction(),
- ),
- )
- }
- "RUNNING" -> {
- val (current, total, _, remaining) = extra.split("|")
- val finishTime = parseCurrentTime(current) + parseTimeDelta(remaining).toMillis()
- TimerContentModel(
- icon = icon,
- // TODO: b/352142761 - define and use a string resource rather than " Timer".
- // (The UX isn't final so using " Timer" for now).
- name = total.replace("Σ", "") + " Timer",
- state =
- TimerContentModel.TimerState.Running(
- finishTime = finishTime,
- pauseIntent = notification.findPauseIntent(),
- addMinuteAction = notification.findAddMinuteAction(),
- resetAction = notification.findResetAction(),
- ),
- )
- }
- else -> error("unknown state ($state) in sortKey=$sortKey")
- }
- }
-
- private fun Notification.findPauseIntent(): PendingIntent? {
- return actions
- .firstOrNull { it.actionIntent.intent?.action?.endsWith(".PAUSE_TIMER") == true }
- ?.actionIntent
- }
-
- private fun Notification.findStartIntent(): PendingIntent? {
- return actions
- .firstOrNull { it.actionIntent.intent?.action?.endsWith(".START_TIMER") == true }
- ?.actionIntent
- }
-
- // TODO: b/352142761 - switch to system attributes for label and icon.
- // - We probably want a consistent look for the Reset button. (Double check with UX.)
- // - Using the custom assets now since I couldn't an existing "Reset" icon.
- private fun Notification.findResetAction(): Notification.Action? {
- return actions.firstOrNull {
- it.actionIntent.intent?.action?.endsWith(".RESET_TIMER") == true
- }
- }
-
- // TODO: b/352142761 - check with UX on whether this should be required.
- // - Alternative is to allow for optional actions in addition to main and reset.
- // - For optional actions, we should take the custom label and icon.
- private fun Notification.findAddMinuteAction(): Notification.Action? {
- return actions.firstOrNull {
- it.actionIntent.intent?.action?.endsWith(".ADD_MINUTE_TIMER") == true
- }
- }
-
- private fun parseCurrentTime(current: String): Long {
- val (hour, minute, second, millis) = current.replace("▶", "").split(":", ".")
- // NOTE: this won't work correctly at/around midnight. It's just for prototyping.
- val localDateTime =
- LocalDateTime.of(
- LocalDate.now(),
- LocalTime.of(hour.toInt(), minute.toInt(), second.toInt(), millis.toInt() * 1000000),
- )
- val offset = ZoneId.systemDefault().rules.getOffset(localDateTime)
- return localDateTime.toInstant(offset).toEpochMilli()
- }
-
- private fun parseTimeDelta(delta: String): Duration {
- val (hour, minute, second) = delta.replace("Σ", "").replace("⏳", "").split(":")
- return Duration.ofHours(hour.toLong())
- .plusMinutes(minute.toLong())
- .plusSeconds(second.toLong())
- }
-
- private fun parseEnRouteNotification(
- notification: Notification,
- icon: IconModel,
- ): EnRouteContentModel {
- return EnRouteContentModel(
- smallIcon = icon,
- title = notification.extras.getCharSequence(Notification.EXTRA_TITLE),
- text = notification.extras.getCharSequence(Notification.EXTRA_TEXT),
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
deleted file mode 100644
index 77c4130482c8..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row
-
-import android.app.Notification
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
-import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
-import com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
-import com.android.systemui.statusbar.notification.row.ui.view.TimerView
-import com.android.systemui.statusbar.notification.row.ui.viewbinder.EnRouteViewBinder
-import com.android.systemui.statusbar.notification.row.ui.viewbinder.TimerViewBinder
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.EnRouteViewModel
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.RichOngoingViewModelComponent
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.TimerViewModel
-import javax.inject.Inject
-import kotlinx.coroutines.DisposableHandle
-
-fun interface DeferredContentViewBinder {
- fun setupContentViewBinder(): DisposableHandle
-}
-
-enum class RichOngoingNotificationViewType {
- Contracted,
- Expanded,
- HeadsUp,
-}
-
-/**
- * * Supertype of the 3 different possible result types of
- * [RichOngoingNotificationViewInflater.inflateView].
- */
-sealed interface ContentViewInflationResult {
-
- /** Indicates that the content view should be removed if present. */
- data object NullContentView : ContentViewInflationResult
-
- /**
- * Indicates that the content view (which *must be* present) should be unmodified during this
- * inflation.
- */
- data object KeepExistingView : ContentViewInflationResult
-
- /**
- * Contains the new view and binder that should replace any existing content view for this slot.
- */
- data class InflatedContentViewHolder(val view: View, val binder: DeferredContentViewBinder) :
- ContentViewInflationResult
-}
-
-fun ContentViewInflationResult?.shouldDisposeViewBinder() = this !is KeepExistingView
-
-/**
- * Interface which provides a [RichOngoingContentModel] for a given [Notification] when one is
- * applicable to the given style.
- */
-interface RichOngoingNotificationViewInflater {
- fun inflateView(
- contentModel: RichOngoingContentModel,
- existingView: View?,
- entry: NotificationEntry,
- systemUiContext: Context,
- parentView: ViewGroup,
- viewType: RichOngoingNotificationViewType,
- ): ContentViewInflationResult
-
- fun canKeepView(
- contentModel: RichOngoingContentModel,
- existingView: View?,
- viewType: RichOngoingNotificationViewType
- ): Boolean
-}
-
-@SysUISingleton
-class RichOngoingNotificationViewInflaterImpl
-@Inject
-constructor(
- private val viewModelComponentFactory: RichOngoingViewModelComponent.Factory,
-) : RichOngoingNotificationViewInflater {
-
- override fun inflateView(
- contentModel: RichOngoingContentModel,
- existingView: View?,
- entry: NotificationEntry,
- systemUiContext: Context,
- parentView: ViewGroup,
- viewType: RichOngoingNotificationViewType,
- ): ContentViewInflationResult {
- if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return NullContentView
- val component = viewModelComponentFactory.create(entry)
- return when (contentModel) {
- is TimerContentModel ->
- inflateTimerView(
- existingView,
- component::createTimerViewModel,
- systemUiContext,
- parentView,
- viewType
- )
- is EnRouteContentModel ->
- inflateEnRouteView(
- existingView,
- component::createEnRouteViewModel,
- systemUiContext,
- parentView,
- viewType
- )
- else -> TODO("Not yet implemented")
- }
- }
-
- override fun canKeepView(
- contentModel: RichOngoingContentModel,
- existingView: View?,
- viewType: RichOngoingNotificationViewType
- ): Boolean {
- if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return false
- return when (contentModel) {
- is TimerContentModel -> canKeepTimerView(contentModel, existingView, viewType)
- is EnRouteContentModel -> canKeepEnRouteView(contentModel, existingView, viewType)
- else -> TODO("Not yet implemented")
- }
- }
-
- private fun inflateTimerView(
- existingView: View?,
- createViewModel: () -> TimerViewModel,
- systemUiContext: Context,
- parentView: ViewGroup,
- viewType: RichOngoingNotificationViewType,
- ): ContentViewInflationResult {
- if (existingView is TimerView && !existingView.isReinflateNeeded()) return KeepExistingView
-
- return when (viewType) {
- RichOngoingNotificationViewType.Contracted -> {
- val newView =
- LayoutInflater.from(systemUiContext)
- .inflate(
- R.layout.rich_ongoing_timer_notification,
- parentView,
- /* attachToRoot= */ false
- ) as TimerView
- InflatedContentViewHolder(newView) {
- TimerViewBinder.bindWhileAttached(newView, createViewModel())
- }
- }
- RichOngoingNotificationViewType.Expanded,
- RichOngoingNotificationViewType.HeadsUp -> NullContentView
- }
- }
-
- private fun canKeepTimerView(
- contentModel: TimerContentModel,
- existingView: View?,
- viewType: RichOngoingNotificationViewType
- ): Boolean = true
-
- private fun inflateEnRouteView(
- existingView: View?,
- createViewModel: () -> EnRouteViewModel,
- systemUiContext: Context,
- parentView: ViewGroup,
- viewType: RichOngoingNotificationViewType,
- ): ContentViewInflationResult {
- if (existingView is EnRouteView && !existingView.isReinflateNeeded())
- return KeepExistingView
- return when (viewType) {
- RichOngoingNotificationViewType.Contracted -> {
- val newView =
- LayoutInflater.from(systemUiContext)
- .inflate(
- R.layout.notification_template_en_route_contracted,
- parentView,
- /* attachToRoot= */ false
- ) as EnRouteView
- InflatedContentViewHolder(newView) {
- EnRouteViewBinder.bindWhileAttached(newView, createViewModel())
- }
- }
- RichOngoingNotificationViewType.Expanded -> {
- val newView =
- LayoutInflater.from(systemUiContext)
- .inflate(
- R.layout.notification_template_en_route_expanded,
- parentView,
- /* attachToRoot= */ false
- ) as EnRouteView
- InflatedContentViewHolder(newView) {
- EnRouteViewBinder.bindWhileAttached(newView, createViewModel())
- }
- }
- RichOngoingNotificationViewType.HeadsUp -> NullContentView
- }
- }
-
- private fun canKeepEnRouteView(
- contentModel: EnRouteContentModel,
- existingView: View?,
- viewType: RichOngoingNotificationViewType
- ): Boolean = true
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepository.kt
deleted file mode 100644
index bac887ba7f14..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepository.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.data.repository
-
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import kotlinx.coroutines.flow.StateFlow
-
-/** A repository of states relating to a specific notification row. */
-interface NotificationRowRepository {
- /**
- * A flow of an immutable data class with the current state of the Rich Ongoing Notification
- * content, if applicable.
- */
- val richOngoingContentModel: StateFlow<RichOngoingContentModel?>
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
deleted file mode 100644
index 72823a760197..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.domain.interactor
-
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filterIsInstance
-
-/** Interactor specific to a particular notification row. */
-class NotificationRowInteractor @Inject constructor(repository: NotificationRowRepository) {
- /** Content of a rich ongoing timer notification. */
- val timerContentModel: Flow<TimerContentModel> =
- repository.richOngoingContentModel.filterIsInstance<TimerContentModel>()
-
- /** Content of a rich ongoing timer notification. */
- val enRouteContentModel: Flow<EnRouteContentModel> =
- repository.richOngoingContentModel.filterIsInstance<EnRouteContentModel>()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt
deleted file mode 100644
index 7e78cca028ef..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.shared
-
-/**
- * Represents something en route.
- *
- * @param smallIcon the main small icon of the EnRoute notification.
- * @param title the title of the EnRoute notification.
- * @param text the text of the EnRoute notification.
- */
-data class EnRouteContentModel(
- val smallIcon: IconModel,
- val title: CharSequence?,
- val text: CharSequence?,
-) : RichOngoingContentModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/IconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/IconModel.kt
deleted file mode 100644
index e61193892d10..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/IconModel.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.shared
-
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.Icon
-
-// TODO: figure out how to support lazy resolution of the drawable, e.g. on unrelated text change
-class IconModel(val icon: Icon) {
- var drawable: Drawable? = null
-
- override fun equals(other: Any?): Boolean =
- when (other) {
- null -> false
- (other === this) -> true
- !is IconModel -> false
- else -> other.icon.sameAs(icon)
- }
-
- override fun toString(): String = "IconModel(icon=$icon, drawable=$drawable)"
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt
index 0f9a5a30cbe5..004c66b64b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt
@@ -22,7 +22,4 @@ data class NotificationContentModel(
val headsUpStatusBarModel: HeadsUpStatusBarModel,
val singleLineViewModel: SingleLineViewModel? = null,
val publicSingleLineViewModel: SingleLineViewModel? = null,
- val richOngoingContentModel: RichOngoingContentModel? = null,
)
-
-sealed interface RichOngoingContentModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt
deleted file mode 100644
index 33b256456ca3..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.shared
-
-import android.app.Notification
-import android.app.PendingIntent
-import java.time.Duration
-
-/**
- * Represents a simple timer that counts down to a time.
- *
- * @param name the label for the timer
- * @param state state of the timer, including time and whether it is paused or running
- */
-data class TimerContentModel(
- val icon: IconModel,
- val name: String,
- val state: TimerState,
-) : RichOngoingContentModel {
- /** The state (paused or running) of the timer, and relevant time */
- sealed interface TimerState {
- val addMinuteAction: Notification.Action?
- val resetAction: Notification.Action?
-
- /**
- * Indicates a running timer
- *
- * @param finishTime the time in ms since epoch that the timer will finish
- * @param pauseIntent the action for pausing the timer
- */
- data class Running(
- val finishTime: Long,
- val pauseIntent: PendingIntent?,
- override val addMinuteAction: Notification.Action?,
- override val resetAction: Notification.Action?,
- ) : TimerState
-
- /**
- * Indicates a paused timer
- *
- * @param timeRemaining the time in ms remaining on the paused timer
- * @param resumeIntent the action for resuming the timer
- */
- data class Paused(
- val timeRemaining: Duration,
- val resumeIntent: PendingIntent?,
- override val addMinuteAction: Notification.Action?,
- override val resetAction: Notification.Action?,
- ) : TimerState
- }
-}
-
-/**
- * Represents a simple stopwatch that counts up and allows tracking laps.
- *
- * @param state state of the stopwatch, including time and whether it is paused or running
- * @param lapDurations a list of durations of each completed lap
- */
-data class StopwatchContentModel(
- val icon: IconModel,
- val state: StopwatchState,
- val lapDurations: List<Long>,
-) : RichOngoingContentModel {
- /** The state (paused or running) of the stopwatch, and relevant time */
- sealed interface StopwatchState {
- /**
- * Indicates a running stopwatch
- *
- * @param startTime the time in ms since epoch that the stopwatch started, plus any
- * accumulated pause time
- * @param pauseIntent the action for pausing the stopwatch
- */
- data class Running(
- val startTime: Long,
- val pauseIntent: PendingIntent,
- ) : StopwatchState
-
- /**
- * Indicates a paused stopwatch
- *
- * @param timeElapsed the time in ms elapsed on the stopwatch
- * @param resumeIntent the action for resuming the stopwatch
- */
- data class Paused(
- val timeElapsed: Duration,
- val resumeIntent: PendingIntent,
- ) : StopwatchState
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingNotificationFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingNotificationFlag.kt
deleted file mode 100644
index 4a7f7cd46ad1..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingNotificationFlag.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.shared
-
-import android.app.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the api rich ongoing flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object RichOngoingNotificationFlag {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_API_RICH_ONGOING
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the refactor enabled */
- @JvmStatic
- inline val isEnabled
- get() = Flags.apiRichOngoing()
-
- /**
- * Called to ensure code is only run when the flag is enabled. This protects users from the
- * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
- * build to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is disabled. This will throw an exception if
- * the flag is enabled to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/ConfigurationTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/ConfigurationTracker.kt
deleted file mode 100644
index 95c507cb72a2..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/ConfigurationTracker.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.ui.view
-
-import android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS
-import android.content.pm.ActivityInfo.CONFIG_DENSITY
-import android.content.pm.ActivityInfo.CONFIG_FONT_SCALE
-import android.content.pm.ActivityInfo.CONFIG_LAYOUT_DIRECTION
-import android.content.pm.ActivityInfo.CONFIG_LOCALE
-import android.content.pm.ActivityInfo.CONFIG_UI_MODE
-import android.content.res.Configuration
-import android.content.res.Resources
-
-/**
- * Tracks the active configuration when constructed and returns (when queried) whether the
- * configuration has unhandled changes.
- */
-class ConfigurationTracker(
- private val resources: Resources,
- private val unhandledConfigChanges: Int
-) {
- private val initialConfig = Configuration(resources.configuration)
-
- constructor(
- resources: Resources,
- handlesDensityFontScale: Boolean = false,
- handlesTheme: Boolean = false,
- handlesLocaleAndLayout: Boolean = true,
- ) : this(
- resources,
- unhandledConfigChanges =
- (if (handlesDensityFontScale) 0 else CONFIG_DENSITY or CONFIG_FONT_SCALE) or
- (if (handlesTheme) 0 else CONFIG_ASSETS_PATHS or CONFIG_UI_MODE) or
- (if (handlesLocaleAndLayout) 0 else CONFIG_LOCALE or CONFIG_LAYOUT_DIRECTION)
- )
-
- /**
- * Whether the current configuration has unhandled changes relative to the initial configuration
- */
- fun hasUnhandledConfigChange(): Boolean =
- initialConfig.diff(resources.configuration) and unhandledConfigChanges != 0
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt
deleted file mode 100644
index e5c2b5fff5e9..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.ui.view
-
-import android.content.Context
-import android.graphics.drawable.Icon
-import android.util.AttributeSet
-import android.widget.FrameLayout
-import android.widget.ImageView
-import android.widget.TextView
-import com.android.internal.R
-import com.android.internal.widget.NotificationExpandButton
-
-class EnRouteView
-@JvmOverloads
-constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0,
- defStyleRes: Int = 0,
-) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
-
- private val configTracker = ConfigurationTracker(resources)
-
- private lateinit var icon: ImageView
- private lateinit var title: TextView
- private lateinit var text: TextView
- private lateinit var expandButton: NotificationExpandButton
-
- override fun onFinishInflate() {
- super.onFinishInflate()
- icon = requireViewById(R.id.icon)
- title = requireViewById(R.id.title)
- text = requireViewById(R.id.text)
-
- expandButton = requireViewById(R.id.expand_button)
- expandButton.setExpanded(false)
- }
-
- /** the resources configuration has changed such that the view needs to be reinflated */
- fun isReinflateNeeded(): Boolean = configTracker.hasUnhandledConfigChange()
-
- fun setIcon(icon: Icon?) {
- this.icon.setImageIcon(icon)
- }
-
- fun setTitle(title: CharSequence?) {
- this.title.text = title
- }
-
- fun setText(text: CharSequence?) {
- this.text.text = text
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt
deleted file mode 100644
index 8c951877544c..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.ui.view
-
-import android.annotation.DrawableRes
-import android.content.Context
-import android.graphics.BlendMode
-import android.util.AttributeSet
-import com.android.internal.widget.EmphasizedNotificationButton
-
-class TimerButtonView
-@JvmOverloads
-constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0,
- defStyleRes: Int = 0,
-) : EmphasizedNotificationButton(context, attrs, defStyleAttr, defStyleRes) {
-
- private val Int.dp: Int
- get() = (this * context.resources.displayMetrics.density).toInt()
-
- fun setIcon(@DrawableRes icon: Int) {
- val drawable = context.getDrawable(icon)
-
- drawable?.mutate()
- drawable?.setTintList(textColors)
- drawable?.setTintBlendMode(BlendMode.SRC_IN)
- drawable?.setBounds(0, 0, 24.dp, 24.dp)
-
- setCompoundDrawablesRelative(drawable, null, null, null)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt
deleted file mode 100644
index d481b50101c1..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.ui.view
-
-import android.content.Context
-import android.graphics.drawable.Icon
-import android.os.SystemClock
-import android.util.AttributeSet
-import android.widget.Chronometer
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.view.isVisible
-import com.android.systemui.res.R
-
-class TimerView
-@JvmOverloads
-constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0,
- defStyleRes: Int = 0,
-) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) {
-
- private val configTracker = ConfigurationTracker(resources)
-
- private lateinit var icon: ImageView
- private lateinit var label: TextView
- private lateinit var chronometer: Chronometer
- private lateinit var pausedTimeRemaining: TextView
- lateinit var mainButton: TimerButtonView
- private set
-
- lateinit var altButton: TimerButtonView
- private set
-
- lateinit var resetButton: TimerButtonView
- private set
-
- override fun onFinishInflate() {
- super.onFinishInflate()
- icon = requireViewById(R.id.icon)
- label = requireViewById(R.id.label)
- chronometer = requireViewById(R.id.chronoRemaining)
- pausedTimeRemaining = requireViewById(R.id.pausedTimeRemaining)
- mainButton = requireViewById(R.id.mainButton)
- altButton = requireViewById(R.id.altButton)
- resetButton = requireViewById(R.id.resetButton)
- }
-
- /** the resources configuration has changed such that the view needs to be reinflated */
- fun isReinflateNeeded(): Boolean = configTracker.hasUnhandledConfigChange()
-
- fun setIcon(icon: Icon?) {
- this.icon.setImageIcon(icon)
- }
-
- fun setLabel(label: String) {
- this.label.text = label
- }
-
- fun setPausedTime(pausedTime: String?) {
- if (pausedTime != null) {
- pausedTimeRemaining.text = pausedTime
- pausedTimeRemaining.isVisible = true
- } else {
- pausedTimeRemaining.isVisible = false
- }
- }
-
- fun setCountdownTime(countdownTimeMs: Long?) {
- if (countdownTimeMs != null) {
- chronometer.base =
- countdownTimeMs - System.currentTimeMillis() + SystemClock.elapsedRealtime()
- chronometer.isVisible = true
- chronometer.start()
- } else {
- chronometer.isVisible = false
- chronometer.stop()
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt
deleted file mode 100644
index 3b8957c092f2..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.ui.viewbinder
-
-import androidx.lifecycle.lifecycleScope
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.EnRouteViewModel
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-
-/** Binds a [EnRouteView] to its [view model][EnRouteViewModel]. */
-object EnRouteViewBinder {
- fun bindWhileAttached(
- view: EnRouteView,
- viewModel: EnRouteViewModel,
- ): DisposableHandle {
- return view.repeatWhenAttached { lifecycleScope.launch { bind(view, viewModel) } }
- }
-
- suspend fun bind(
- view: EnRouteView,
- viewModel: EnRouteViewModel,
- ) = coroutineScope {
- launch { viewModel.icon.collect { view.setIcon(it) } }
- launch { viewModel.title.collect { view.setTitle(it) } }
- launch { viewModel.text.collect { view.setText(it) } }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt
deleted file mode 100644
index 042d1bcfb2ee..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.ui.viewbinder
-
-import android.content.res.ColorStateList
-import android.graphics.drawable.Icon
-import android.view.View
-import androidx.core.view.isGone
-import androidx.lifecycle.lifecycleScope
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
-import com.android.systemui.statusbar.notification.row.ui.view.TimerView
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.TimerViewModel
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-
-/** Binds a [TimerView] to its [view model][TimerViewModel]. */
-object TimerViewBinder {
- fun bindWhileAttached(
- view: TimerView,
- viewModel: TimerViewModel,
- ): DisposableHandle {
- return view.repeatWhenAttached { lifecycleScope.launch { bind(view, viewModel) } }
- }
-
- suspend fun bind(
- view: TimerView,
- viewModel: TimerViewModel,
- ) = coroutineScope {
- launch { viewModel.icon.collect { view.setIcon(it) } }
- launch { viewModel.label.collect { view.setLabel(it) } }
- launch { viewModel.pausedTime.collect { view.setPausedTime(it) } }
- launch { viewModel.countdownTime.collect { view.setCountdownTime(it) } }
- launch { viewModel.mainButtonModel.collect { bind(view.mainButton, it) } }
- launch { viewModel.altButtonModel.collect { bind(view.altButton, it) } }
- launch { viewModel.resetButtonModel.collect { bind(view.resetButton, it) } }
- }
-
- fun bind(buttonView: TimerButtonView, model: TimerViewModel.ButtonViewModel?) {
- if (model != null) {
- buttonView.setButtonBackground(
- ColorStateList.valueOf(
- buttonView.context.getColor(com.android.internal.R.color.system_accent2_100)
- )
- )
- buttonView.setTextColor(
- buttonView.context.getColor(
- com.android.internal.R.color.notification_primary_text_color_light
- )
- )
-
- when (model) {
- is TimerViewModel.ButtonViewModel.WithSystemAttrs -> {
- buttonView.setIcon(model.iconRes)
- buttonView.setText(model.labelRes)
- }
- is TimerViewModel.ButtonViewModel.WithCustomAttrs -> {
- // TODO: b/352142761 - is there a better way to deal with TYPE_RESOURCE icons
- // with empty resPackage? RemoteViews handles this by using a different
- // `contextForResources` for inflation.
- val icon =
- if (model.icon.type == Icon.TYPE_RESOURCE && model.icon.resPackage == "")
- Icon.createWithResource(
- "com.google.android.deskclock",
- model.icon.resId
- )
- else model.icon
- buttonView.setImageIcon(icon)
- buttonView.text = model.label
- }
- }
-
- buttonView.setOnClickListener(
- model.pendingIntent?.let { pendingIntent ->
- View.OnClickListener { pendingIntent.send() }
- }
- )
- buttonView.isEnabled = model.pendingIntent != null
- }
- buttonView.isGone = model == null
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt
deleted file mode 100644
index 307a9834ccc9..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.ui.viewmodel
-
-import android.graphics.drawable.Icon
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.row.domain.interactor.NotificationRowInteractor
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.util.kotlin.FlowDumperImpl
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
-
-/** A view model for EnRoute notifications. */
-class EnRouteViewModel
-@Inject
-constructor(
- dumpManager: DumpManager,
- rowInteractor: NotificationRowInteractor,
-) : FlowDumperImpl(dumpManager) {
- init {
- /* check if */ RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()
- }
-
- val icon: Flow<Icon?> = rowInteractor.enRouteContentModel.mapNotNull { it.smallIcon.icon }
-
- val title: Flow<CharSequence?> = rowInteractor.enRouteContentModel.map { it.title }
-
- val text: Flow<CharSequence?> = rowInteractor.enRouteContentModel.map { it.text }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
deleted file mode 100644
index 5552d89b6f9d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.ui.viewmodel
-
-// noinspection CleanArchitectureDependencyViolation
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-import dagger.BindsInstance
-import dagger.Subcomponent
-
-@Subcomponent
-interface RichOngoingViewModelComponent {
-
- @Subcomponent.Factory
- interface Factory {
- /** Creates an instance of [RichOngoingViewModelComponent]. */
- fun create(
- @BindsInstance repository: NotificationRowRepository
- ): RichOngoingViewModelComponent
- }
-
- fun createTimerViewModel(): TimerViewModel
-
- fun createEnRouteViewModel(): EnRouteViewModel
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt
deleted file mode 100644
index 768a093e0b65..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.ui.viewmodel
-
-import android.annotation.DrawableRes
-import android.annotation.StringRes
-import android.app.PendingIntent
-import android.graphics.drawable.Icon
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.row.domain.interactor.NotificationRowInteractor
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel.TimerState
-import com.android.systemui.util.kotlin.FlowDumperImpl
-import java.time.Duration
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
-
-/** A view model for Timer notifications. */
-class TimerViewModel
-@Inject
-constructor(
- dumpManager: DumpManager,
- rowInteractor: NotificationRowInteractor,
-) : FlowDumperImpl(dumpManager) {
- init {
- /* check if */ RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()
- }
-
- private val state: Flow<TimerState> = rowInteractor.timerContentModel.mapNotNull { it.state }
-
- val icon: Flow<Icon?> = rowInteractor.timerContentModel.mapNotNull { it.icon.icon }
-
- val label: Flow<String> = rowInteractor.timerContentModel.mapNotNull { it.name }
-
- val countdownTime: Flow<Long?> = state.map { (it as? TimerState.Running)?.finishTime }
-
- val pausedTime: Flow<String?> =
- state.map { (it as? TimerState.Paused)?.timeRemaining?.format() }
-
- val mainButtonModel: Flow<ButtonViewModel> =
- state.map {
- when (it) {
- is TimerState.Paused ->
- ButtonViewModel.WithSystemAttrs(
- it.resumeIntent,
- com.android.systemui.res.R.string.controls_media_resume, // "Resume",
- com.android.systemui.res.R.drawable.ic_media_play
- )
- is TimerState.Running ->
- ButtonViewModel.WithSystemAttrs(
- it.pauseIntent,
- com.android.systemui.res.R.string.controls_media_button_pause, // "Pause",
- com.android.systemui.res.R.drawable.ic_media_pause
- )
- }
- }
-
- val altButtonModel: Flow<ButtonViewModel?> =
- state.map {
- it.addMinuteAction?.let { action ->
- ButtonViewModel.WithCustomAttrs(
- action.actionIntent,
- action.title, // "1:00",
- action.getIcon()
- )
- }
- }
-
- val resetButtonModel: Flow<ButtonViewModel?> =
- state.map {
- it.resetAction?.let { action ->
- ButtonViewModel.WithCustomAttrs(
- action.actionIntent,
- action.title, // "Reset",
- action.getIcon()
- )
- }
- }
-
- sealed interface ButtonViewModel {
- val pendingIntent: PendingIntent?
-
- data class WithSystemAttrs(
- override val pendingIntent: PendingIntent?,
- @StringRes val labelRes: Int,
- @DrawableRes val iconRes: Int,
- ) : ButtonViewModel
-
- data class WithCustomAttrs(
- override val pendingIntent: PendingIntent?,
- val label: CharSequence,
- val icon: Icon,
- ) : ButtonViewModel
- }
-}
-
-private fun Duration.format(): String {
- val hours = this.toHours()
- return if (hours > 0) {
- String.format("%d:%02d:%02d", hours, toMinutesPart(), toSecondsPart())
- } else {
- String.format("%d:%02d", toMinutes(), toSecondsPart())
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 5ae24f76a9bf..479ffb728eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -55,6 +55,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.TrustGrantFlags;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.DejankUtils;
import com.android.systemui.Flags;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
@@ -1203,6 +1204,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Override
public void hide(long startTime, long fadeoutDuration) {
Trace.beginSection("StatusBarKeyguardViewManager#hide");
+ if (Flags.checkLockscreenGoneTransition()) {
+ DejankUtils.notifyRendererOfExpensiveFrame(
+ mNotificationShadeWindowController.getWindowRootView(),
+ "StatusBarKeyguardViewManager#hide");
+ }
mKeyguardStateController.notifyKeyguardState(false,
mKeyguardStateController.isOccluded());
launchPendingWakeupAction();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index eea4c212e40e..9c168be0693f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -95,7 +95,8 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa
primaryChipView.show(shouldAnimateChange = true)
is OngoingActivityChipModel.Hidden ->
primaryChipView.hide(
- shouldAnimateChange = primaryChipModel.shouldAnimate
+ state = View.GONE,
+ shouldAnimateChange = primaryChipModel.shouldAnimate,
)
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 5f6ad9205ec7..02d0b577feb1 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -61,7 +61,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -393,7 +393,7 @@ public final class WMShell implements
void initDesktopMode(DesktopMode desktopMode) {
desktopMode.addVisibleTasksListener(
- new DesktopModeTaskRepository.VisibleTasksListener() {
+ new DesktopRepository.VisibleTasksListener() {
@Override
public void onTasksVisibilityChanged(int displayId, int visibleTasksCount) {
if (displayId == Display.DEFAULT_DISPLAY) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 9cd52153eff6..8206c21fce30 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -85,8 +85,9 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
`when`(telephonyManager.createForSubscriptionId(anyInt())).thenReturn(telephonyManager)
`when`(telephonyManager.supplyIccLockPin(anyString())).thenReturn(mock())
simPinView =
- LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
- as KeyguardSimPinView
+ LayoutInflater.from(context)
+ .inflate(R.layout.keyguard_sim_pin_view, null)
+ .requireViewById(R.id.keyguard_sim_pin_view) as KeyguardSimPinView
val fakeFeatureFlags = FakeFeatureFlags()
val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index a099c9dc6442..48608ebd6de0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -35,9 +35,6 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
@@ -51,33 +48,23 @@ import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedac
import com.android.systemui.statusbar.notification.row.shared.NewRemoteViews
import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
import com.android.systemui.statusbar.policy.InflatedSmartReplyState
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder
import com.android.systemui.statusbar.policy.SmartReplyStateInflater
-import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
-import kotlinx.coroutines.DisposableHandle
import org.junit.Assert
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
-import org.mockito.kotlin.argThat
-import org.mockito.kotlin.clearInvocations
-import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
-import org.mockito.kotlin.inOrder
import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@SmallTest
@@ -118,45 +105,6 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
}
}
- private var fakeRonContentModel: RichOngoingContentModel? = null
- private val fakeRonExtractor =
- object : RichOngoingNotificationContentExtractor {
- override fun extractContentModel(
- entry: NotificationEntry,
- builder: Notification.Builder,
- systemUIContext: Context,
- packageContext: Context
- ): RichOngoingContentModel? = fakeRonContentModel
- }
-
- private var fakeContractedRonViewHolder: ContentViewInflationResult = NullContentView
- private var fakeExpandedRonViewHolder: ContentViewInflationResult = NullContentView
- private var fakeHeadsUpRonViewHolder: ContentViewInflationResult = NullContentView
- private var fakeRonViewInflater =
- spy(
- object : RichOngoingNotificationViewInflater {
- override fun inflateView(
- contentModel: RichOngoingContentModel,
- existingView: View?,
- entry: NotificationEntry,
- systemUiContext: Context,
- parentView: ViewGroup,
- viewType: RichOngoingNotificationViewType
- ): ContentViewInflationResult =
- when (viewType) {
- RichOngoingNotificationViewType.Contracted -> fakeContractedRonViewHolder
- RichOngoingNotificationViewType.Expanded -> fakeExpandedRonViewHolder
- RichOngoingNotificationViewType.HeadsUp -> fakeHeadsUpRonViewHolder
- }
-
- override fun canKeepView(
- contentModel: RichOngoingContentModel,
- existingView: View?,
- viewType: RichOngoingNotificationViewType
- ): Boolean = false
- }
- )
-
@Before
fun setUp() {
allowTestableLooperAsMainThread()
@@ -167,15 +115,12 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
.setContentText("Text")
.setStyle(Notification.BigTextStyle().bigText("big text"))
testHelper = NotificationTestHelper(mContext, mDependency)
- testHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL)
row = spy(testHelper.createRow(builder.build()))
notificationInflater =
NotificationRowContentBinderImpl(
cache,
mock(),
mock<ConversationNotificationProcessor>(),
- fakeRonExtractor,
- fakeRonViewInflater,
mock(),
smartReplyStateInflater,
layoutInflaterFactoryProvider,
@@ -405,496 +350,6 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
}
@Test
- fun testRonModelRequiredForRonView() {
- fakeRonContentModel = null
- val contractedRonView = View(context)
- val expandedRonView = View(context)
- val headsUpRonView = View(context)
- fakeContractedRonViewHolder =
- InflatedContentViewHolder(view = contractedRonView, binder = mock())
- fakeExpandedRonViewHolder =
- InflatedContentViewHolder(view = expandedRonView, binder = mock())
- fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = headsUpRonView, binder = mock())
-
- // WHEN inflater inflates
- val contentToInflate =
- FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
- inflateAndWait(notificationInflater, contentToInflate, row)
- verifyNoMoreInteractions(fakeRonViewInflater)
- }
-
- @Test
- fun testRonModelCleansUpRemoteViews() {
- val ronView = View(context)
-
- val entry = row.entry
-
- fakeRonContentModel = mock<TimerContentModel>()
- fakeContractedRonViewHolder =
- InflatedContentViewHolder(view = ronView, binder = mock<DeferredContentViewBinder>())
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
- // VERIFY
- verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_CONTRACTED))
- verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_EXPANDED))
- verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_HEADS_UP))
- }
-
- @Test
- fun testRonModelCleansUpSmartReplies() {
- val ronView = View(context)
-
- val privateLayout = spy(row.privateLayout)
-
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mock<TimerContentModel>()
- fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mock())
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
- // VERIFY
- verify(privateLayout).setExpandedInflatedSmartReplies(eq(null))
- verify(privateLayout).setHeadsUpInflatedSmartReplies(eq(null))
- }
-
- @Test
- fun testRonModelTriggersInflationOfContractedRonView() {
- val mockRonModel = mock<TimerContentModel>()
- val ronView = View(context)
- val mockBinder = mock<DeferredContentViewBinder>()
-
- val entry = row.entry
- val privateLayout = row.privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
- // VERIFY that the inflater is invoked
- verify(fakeRonViewInflater)
- .inflateView(
- eq(mockRonModel),
- any(),
- eq(entry),
- any(),
- eq(privateLayout),
- eq(RichOngoingNotificationViewType.Contracted)
- )
- assertThat(row.privateLayout.contractedChild).isSameInstanceAs(ronView)
- verify(mockBinder).setupContentViewBinder()
- }
-
- @Test
- fun testRonModelTriggersInflationOfExpandedRonView() {
- val mockRonModel = mock<TimerContentModel>()
- val ronView = View(context)
- val mockBinder = mock<DeferredContentViewBinder>()
-
- val entry = row.entry
- val privateLayout = row.privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeExpandedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
-
- // VERIFY that the inflater is invoked
- verify(fakeRonViewInflater)
- .inflateView(
- eq(mockRonModel),
- any(),
- eq(entry),
- any(),
- eq(privateLayout),
- eq(RichOngoingNotificationViewType.Expanded)
- )
- assertThat(row.privateLayout.expandedChild).isSameInstanceAs(ronView)
- verify(mockBinder).setupContentViewBinder()
- }
-
- @Test
- fun testRonModelTriggersInflationOfHeadsUpRonView() {
- val mockRonModel = mock<TimerContentModel>()
- val ronView = View(context)
- val mockBinder = mock<DeferredContentViewBinder>()
-
- val entry = row.entry
- val privateLayout = row.privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
-
- // VERIFY that the inflater is invoked
- verify(fakeRonViewInflater)
- .inflateView(
- eq(mockRonModel),
- any(),
- eq(entry),
- any(),
- eq(privateLayout),
- eq(RichOngoingNotificationViewType.HeadsUp)
- )
- assertThat(row.privateLayout.headsUpChild).isSameInstanceAs(ronView)
- verify(mockBinder).setupContentViewBinder()
- }
-
- @Test
- fun keepExistingViewForContractedRonNotChangingContractedChild() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
-
- row.privateLayout.mContractedBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeContractedRonViewHolder = KeepExistingView
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
- // THEN do not dispose old contracted binder handle and change contracted child
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verifyNoMoreInteractions(oldHandle)
- verify(privateLayout, never()).setContractedChild(any())
- }
-
- @Test
- fun keepExistingViewForExpandedRonNotChangingExpandedChild() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
-
- row.privateLayout.mExpandedBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeExpandedRonViewHolder = KeepExistingView
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
-
- // THEN do not dispose old expanded binder handle and change expanded child
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verifyNoMoreInteractions(oldHandle)
- verify(privateLayout, never()).setExpandedChild(any())
- }
-
- @Test
- fun keepExistingViewForHeadsUpRonNotChangingHeadsUpChild() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
-
- row.privateLayout.mHeadsUpBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeHeadsUpRonViewHolder = KeepExistingView
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
-
- // THEN - do not dispose old heads up binder handle and change heads up child
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verifyNoMoreInteractions(oldHandle)
- verify(privateLayout, never()).setHeadsUpChild(any())
- }
-
- @Test
- fun nullContentViewForContractedRonAppliesElementsInOrder() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
-
- row.privateLayout.mContractedBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeContractedRonViewHolder = NullContentView
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
- // Validate that these 4 steps happen in this precise order
- inOrder(oldHandle, entry, privateLayout, cache) {
- verify(oldHandle).dispose()
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verify(privateLayout).setContractedChild(eq(null))
- }
- }
-
- @Test
- fun nullContentViewForExpandedRonAppliesElementsInOrder() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
-
- row.privateLayout.mExpandedBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeExpandedRonViewHolder = NullContentView
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
-
- // Validate that these 4 steps happen in this precise order
- inOrder(oldHandle, entry, privateLayout, cache) {
- verify(oldHandle).dispose()
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verify(privateLayout).setExpandedChild(eq(null))
- }
- }
-
- @Test
- fun nullContentViewForHeadsUpRonAppliesElementsInOrder() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
-
- row.privateLayout.mHeadsUpBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeHeadsUpRonViewHolder = NullContentView
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
-
- // Validate that these 4 steps happen in this precise order
- inOrder(oldHandle, entry, privateLayout, cache) {
- verify(oldHandle).dispose()
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verify(privateLayout).setHeadsUpChild(eq(null))
- }
- }
-
- @Test
- fun contractedRonViewAppliesElementsInOrder() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
- val ronView = View(context)
- val mockBinder = mock<DeferredContentViewBinder>()
-
- row.privateLayout.mContractedBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
- // Validate that these 4 steps happen in this precise order
- inOrder(oldHandle, entry, privateLayout, mockBinder) {
- verify(oldHandle).dispose()
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verify(privateLayout).setContractedChild(eq(ronView))
- verify(mockBinder).setupContentViewBinder()
- }
- }
-
- @Test
- fun expandedRonViewAppliesElementsInOrder() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
- val ronView = View(context)
- val mockBinder = mock<DeferredContentViewBinder>()
-
- row.privateLayout.mExpandedBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeExpandedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
-
- // Validate that these 4 steps happen in this precise order
- inOrder(oldHandle, entry, privateLayout, mockBinder) {
- verify(oldHandle).dispose()
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verify(privateLayout).setExpandedChild(eq(ronView))
- verify(mockBinder).setupContentViewBinder()
- }
- }
-
- @Test
- fun headsUpRonViewAppliesElementsInOrder() {
- val oldHandle = mock<DisposableHandle>()
- val mockRonModel = mock<TimerContentModel>()
- val ronView = View(context)
- val mockBinder = mock<DeferredContentViewBinder>()
-
- row.privateLayout.mHeadsUpBinderHandle = oldHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- fakeRonContentModel = mockRonModel
- fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
- // WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
-
- // Validate that these 4 steps happen in this precise order
- inOrder(oldHandle, entry, privateLayout, mockBinder) {
- verify(oldHandle).dispose()
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verify(privateLayout).setHeadsUpChild(eq(ronView))
- verify(mockBinder).setupContentViewBinder()
- }
- }
-
- @Test
- fun testRonNotReinflating() {
- val oldContractedBinderHandle = mock<DisposableHandle>()
- val oldExpandedBinderHandle = mock<DisposableHandle>()
- val oldHeadsUpBinderHandle = mock<DisposableHandle>()
-
- val contractedBinderHandle = mock<DisposableHandle>()
- val expandedBinderHandle = mock<DisposableHandle>()
- val headsUpBinderHandle = mock<DisposableHandle>()
-
- val contractedRonView = View(context)
- val expandedRonView = View(context)
- val headsUpRonView = View(context)
-
- val mockRonModel1 = mock<TimerContentModel>()
- val mockRonModel2 = mock<TimerContentModel>()
-
- val mockContractedViewBinder = mock<DeferredContentViewBinder>()
- val mockExpandedViewBinder = mock<DeferredContentViewBinder>()
- val mockHeadsUpViewBinder = mock<DeferredContentViewBinder>()
-
- doReturn(contractedBinderHandle).whenever(mockContractedViewBinder).setupContentViewBinder()
- doReturn(expandedBinderHandle).whenever(mockExpandedViewBinder).setupContentViewBinder()
- doReturn(headsUpBinderHandle).whenever(mockHeadsUpViewBinder).setupContentViewBinder()
-
- row.privateLayout.mContractedBinderHandle = oldContractedBinderHandle
- row.privateLayout.mExpandedBinderHandle = oldExpandedBinderHandle
- row.privateLayout.mHeadsUpBinderHandle = oldHeadsUpBinderHandle
- val entry = spy(row.entry)
- row.entry = entry
- val privateLayout = spy(row.privateLayout)
- row.privateLayout = privateLayout
-
- // WHEN inflater inflates both a model and a view
- fakeRonContentModel = mockRonModel1
- fakeContractedRonViewHolder =
- InflatedContentViewHolder(view = contractedRonView, binder = mockContractedViewBinder)
- fakeExpandedRonViewHolder =
- InflatedContentViewHolder(view = expandedRonView, binder = mockExpandedViewBinder)
- fakeHeadsUpRonViewHolder =
- InflatedContentViewHolder(view = headsUpRonView, binder = mockHeadsUpViewBinder)
-
- val contentToInflate =
- FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
- inflateAndWait(notificationInflater, contentToInflate, row)
-
- // Validate that these 4 steps happen in this precise order
- inOrder(
- oldContractedBinderHandle,
- oldExpandedBinderHandle,
- oldHeadsUpBinderHandle,
- entry,
- privateLayout,
- mockContractedViewBinder,
- mockExpandedViewBinder,
- mockHeadsUpViewBinder,
- contractedBinderHandle,
- expandedBinderHandle,
- headsUpBinderHandle
- ) {
- verify(oldContractedBinderHandle).dispose()
- verify(oldExpandedBinderHandle).dispose()
- verify(oldHeadsUpBinderHandle).dispose()
-
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel1 })
-
- verify(privateLayout).setContractedChild(eq(contractedRonView))
- verify(mockContractedViewBinder).setupContentViewBinder()
-
- verify(privateLayout).setExpandedChild(eq(expandedRonView))
- verify(mockExpandedViewBinder).setupContentViewBinder()
-
- verify(privateLayout).setHeadsUpChild(eq(headsUpRonView))
- verify(mockHeadsUpViewBinder).setupContentViewBinder()
-
- verify(contractedBinderHandle, never()).dispose()
- verify(expandedBinderHandle, never()).dispose()
- verify(headsUpBinderHandle, never()).dispose()
- }
-
- clearInvocations(
- oldContractedBinderHandle,
- oldExpandedBinderHandle,
- oldHeadsUpBinderHandle,
- entry,
- privateLayout,
- mockContractedViewBinder,
- mockExpandedViewBinder,
- mockHeadsUpViewBinder,
- contractedBinderHandle,
- expandedBinderHandle,
- headsUpBinderHandle
- )
-
- // THEN when the inflater inflates just a model
- fakeRonContentModel = mockRonModel2
- fakeContractedRonViewHolder = KeepExistingView
- fakeExpandedRonViewHolder = KeepExistingView
- fakeHeadsUpRonViewHolder = KeepExistingView
-
- inflateAndWait(notificationInflater, contentToInflate, row)
-
- // Validate that for reinflation, the only thing we do us update the model
- verify(contractedBinderHandle, never()).dispose()
- verify(expandedBinderHandle, never()).dispose()
- verify(headsUpBinderHandle, never()).dispose()
- verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel2 })
- verify(privateLayout, never()).setContractedChild(any())
- verify(privateLayout, never()).setExpandedChild(any())
- verify(privateLayout, never()).setHeadsUpChild(any())
- verify(mockContractedViewBinder, never()).setupContentViewBinder()
- verify(mockExpandedViewBinder, never()).setupContentViewBinder()
- verify(mockHeadsUpViewBinder, never()).setupContentViewBinder()
- verify(contractedBinderHandle, never()).dispose()
- verify(expandedBinderHandle, never()).dispose()
- verify(headsUpBinderHandle, never()).dispose()
- }
-
- @Test
fun testNotificationViewHeightTooSmallFailsValidation() {
val validationError =
getValidationError(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 75376e62f192..2340d0289db4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -200,8 +200,6 @@ public class NotificationTestHelper {
mock(NotifRemoteViewCache.class),
mock(NotificationRemoteInputManager.class),
mock(ConversationNotificationProcessor.class),
- mock(RichOngoingNotificationContentExtractor.class),
- mock(RichOngoingNotificationViewInflater.class),
mock(Executor.class),
new MockSmartReplyInflater(),
mock(NotifLayoutInflaterFactory.Provider.class),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 2eb1573dc3d7..fc4f05df26ed 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -222,8 +222,6 @@ class ExpandableNotificationRowBuilder(
Mockito.mock(NotifRemoteViewCache::class.java, STUB_ONLY),
remoteInputManager,
conversationProcessor,
- Mockito.mock(RichOngoingNotificationContentExtractor::class.java, STUB_ONLY),
- Mockito.mock(RichOngoingNotificationViewInflater::class.java, STUB_ONLY),
Mockito.mock(Executor::class.java, STUB_ONLY),
smartReplyStateInflater,
notifLayoutInflaterFactoryProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepositoryKosmos.kt
deleted file mode 100644
index 84ef4b5c21c3..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepositoryKosmos.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.data.repository
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import kotlinx.coroutines.flow.MutableStateFlow
-
-val Kosmos.fakeNotificationRowRepository by Fixture { FakeNotificationRowRepository() }
-
-class FakeNotificationRowRepository : NotificationRowRepository {
- override val richOngoingContentModel = MutableStateFlow<RichOngoingContentModel?>(null)
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
deleted file mode 100644
index 3a7d7ba064d0..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.domain.interactor
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-
-fun Kosmos.getNotificationRowInteractor(repository: NotificationRowRepository) =
- NotificationRowInteractor(repository = repository)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt
deleted file mode 100644
index 7e511355372b..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.ui.viewmodel
-
-import com.android.systemui.dump.dumpManager
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-import com.android.systemui.statusbar.notification.row.domain.interactor.getNotificationRowInteractor
-
-fun Kosmos.getEnRouteViewModel(repository: NotificationRowRepository) =
- EnRouteViewModel(
- dumpManager = dumpManager,
- rowInteractor = getNotificationRowInteractor(repository),
- )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelKosmos.kt
deleted file mode 100644
index 00f45b220654..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelKosmos.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * 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.systemui.statusbar.notification.row.ui.viewmodel
-
-import com.android.systemui.dump.dumpManager
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-import com.android.systemui.statusbar.notification.row.domain.interactor.getNotificationRowInteractor
-
-fun Kosmos.getTimerViewModel(repository: NotificationRowRepository) =
- TimerViewModel(
- dumpManager = dumpManager,
- rowInteractor = getNotificationRowInteractor(repository),
- )
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 6fd281e7003e..f5a297bfd4f5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -685,11 +685,6 @@ final class ActivityManagerConstants extends ContentObserver {
// default. Controlled by Settings.Global.FORCE_ENABLE_PSS_PROFILING
volatile boolean mForceEnablePssProfiling = false;
- // Indicates whether to use ApplicationInfo to determine launched state instead of PM user state
- // This is a temporary workaround until the trunk-stable flag is pushed to nextfood.
- // TODO: b/365979852 - remove this workaround when redundant
- volatile boolean mFlagUseAppInfoNotLaunched = false;
-
/**
* Indicates whether the foreground service background start restriction is enabled for
* caller app that is targeting S+.
@@ -1022,9 +1017,6 @@ final class ActivityManagerConstants extends ContentObserver {
private static final Uri FORCE_ENABLE_PSS_PROFILING_URI =
Settings.Global.getUriFor(Settings.Global.FORCE_ENABLE_PSS_PROFILING);
- private static final Uri ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI =
- Settings.Global.getUriFor(Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED);
-
/**
* The threshold to decide if a given association should be dumped into metrics.
*/
@@ -1487,7 +1479,6 @@ final class ActivityManagerConstants extends ContentObserver {
false, this);
}
mResolver.registerContentObserver(FORCE_ENABLE_PSS_PROFILING_URI, false, this);
- mResolver.registerContentObserver(ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI, false, this);
updateConstants();
if (mSystemServerAutomaticHeapDumpEnabled) {
updateEnableAutomaticSystemServerHeapDumps();
@@ -1504,7 +1495,6 @@ final class ActivityManagerConstants extends ContentObserver {
updateActivityStartsLoggingEnabled();
updateForegroundServiceStartsLoggingEnabled();
updateForceEnablePssProfiling();
- updateEnableUseAppInfoNotLaunched();
// Read DropboxRateLimiter params from flags.
mService.initDropboxRateLimiter();
}
@@ -1550,8 +1540,6 @@ final class ActivityManagerConstants extends ContentObserver {
updateEnableAutomaticSystemServerHeapDumps();
} else if (FORCE_ENABLE_PSS_PROFILING_URI.equals(uri)) {
updateForceEnablePssProfiling();
- } else if (ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI.equals(uri)) {
- updateEnableUseAppInfoNotLaunched();
}
}
@@ -1671,11 +1659,6 @@ final class ActivityManagerConstants extends ContentObserver {
Settings.Global.FORCE_ENABLE_PSS_PROFILING, 0) == 1;
}
- private void updateEnableUseAppInfoNotLaunched() {
- mFlagUseAppInfoNotLaunched = Settings.Global.getInt(mResolver,
- Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED, 0) == 1;
- }
-
private void updateBackgroundActivityStarts() {
mFlagBackgroundActivityStartsEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2555,8 +2538,6 @@ final class ActivityManagerConstants extends ContentObserver {
pw.print(" OOMADJ_UPDATE_QUICK="); pw.println(OOMADJ_UPDATE_QUICK);
pw.print(" ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION=");
pw.println(mEnableWaitForFinishAttachApplication);
- pw.print(" FLAG_USE_APP_INFO_NOT_LAUNCHED=");
- pw.println(mFlagUseAppInfoNotLaunched);
pw.print(" "); pw.print(KEY_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION);
pw.print("="); pw.println(FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION);
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index a93ae72fcfea..57922d5eb05e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3401,8 +3401,7 @@ public final class ProcessList {
// Check if we should mark the processrecord for first launch after force-stopping
if (wasStopped) {
boolean wasEverLaunched = false;
- if (android.app.Flags.useAppInfoNotLaunched()
- || mService.mConstants.mFlagUseAppInfoNotLaunched) {
+ if (android.app.Flags.useAppInfoNotLaunched()) {
wasEverLaunched = !info.isNotLaunched();
} else {
try {
@@ -3423,8 +3422,7 @@ public final class ProcessList {
: STOPPED_STATE_FIRST_LAUNCH;
r.getWindowProcessController().setStoppedState(stoppedState);
} else {
- if (android.app.Flags.useAppInfoNotLaunched()
- || mService.mConstants.mFlagUseAppInfoNotLaunched) {
+ if (android.app.Flags.useAppInfoNotLaunched()) {
// If it was launched before, then it must be a force-stop
r.setWasForceStopped(wasEverLaunched);
} else {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index bb503aaab471..3dc531e86da1 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -5233,10 +5233,9 @@ public final class DisplayManagerService extends SystemService {
}
@Override
- public boolean isProximitySensorAvailable() {
+ public boolean isProximitySensorAvailable(int displayId) {
synchronized (mSyncRoot) {
- return mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY)
- .isProximitySensorAvailable();
+ return mDisplayPowerControllers.get(displayId).isProximitySensorAvailable();
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 3780fbd61e79..bbdac5636fa4 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -99,6 +99,7 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -126,6 +127,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.LongSparseArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -253,6 +255,8 @@ public class LockSettingsService extends ILockSettings.Stub {
private static final String MIGRATED_KEYSTORE_NS = "migrated_keystore_namespace";
private static final String MIGRATED_SP_CE_ONLY = "migrated_all_users_to_sp_and_bound_ce";
private static final String MIGRATED_SP_FULL = "migrated_all_users_to_sp_and_bound_keys";
+ private static final String MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS =
+ "migrated_weaver_disabled_on_unsecured_users";
// Duration that LockSettingsService will store the gatekeeper password for. This allows
// multiple biometric enrollments without prompting the user to enter their password via
@@ -309,6 +313,10 @@ public class LockSettingsService extends ILockSettings.Stub {
@GuardedBy("mUserCreationAndRemovalLock")
private boolean mThirdPartyAppsStarted;
+ // This list contains the (protectorId, userId) of any protectors that were by replaced by a
+ // migration and should be destroyed once rollback to the old build is no longer possible.
+ private ArrayList<Pair<Long, Integer>> mProtectorsToDestroyOnBootCompleted = new ArrayList<>();
+
// Current password metrics for all secured users on the device. Updated when user unlocks the
// device or changes password. Removed if user is stopped with its CE key evicted.
@GuardedBy("this")
@@ -363,6 +371,10 @@ public class LockSettingsService extends ILockSettings.Stub {
mLockSettingsService.migrateOldDataAfterSystemReady();
mLockSettingsService.deleteRepairModePersistentDataIfNeeded();
} else if (phase == PHASE_BOOT_COMPLETED) {
+ // In the case of an upgrade, PHASE_BOOT_COMPLETED means that a rollback to the old
+ // build can no longer occur. This is the time to destroy any migrated protectors.
+ mLockSettingsService.destroyMigratedProtectors();
+
mLockSettingsService.loadEscrowData();
}
}
@@ -1076,6 +1088,11 @@ public class LockSettingsService extends ILockSettings.Stub {
mStorage.deleteRepairModePersistentData();
}
+ private boolean isWeaverDisabledOnUnsecuredUsers() {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers);
+ }
+
// This is called when Weaver is guaranteed to be available (if the device supports Weaver).
// It does any synthetic password related work that was delayed from earlier in the boot.
private void onThirdPartyAppsStarted() {
@@ -1114,13 +1131,20 @@ public class LockSettingsService extends ILockSettings.Stub {
//
// - Upgrading from Android 14, where unsecured users didn't have Keystore super keys.
//
+ // - Upgrading from a build with config_disableWeaverOnUnsecuredUsers=false to one with
+ // config_disableWeaverOnUnsecuredUsers=true. (We don't bother to proactively add
+ // Weaver for the reverse update to false, as it's too late to help in that case.)
+ //
// The end result is that all users, regardless of whether they are secured or not, have
- // a synthetic password with all keys initialized and protected by it.
+ // a synthetic password with all keys initialized and protected by it, and honoring
+ // config_disableWeaverOnUnsecuredUsers=true when applicable.
//
// Note: if this migration gets interrupted (e.g. by the device powering off), there
// shouldn't be a problem since this will run again on the next boot, and
// setCeStorageProtection() and initKeystoreSuperKeys(..., true) are idempotent.
- if (!getBoolean(MIGRATED_SP_FULL, false, 0)) {
+ if (!getBoolean(MIGRATED_SP_FULL, false, 0)
+ || (isWeaverDisabledOnUnsecuredUsers()
+ && !getBoolean(MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS, false, 0))) {
for (UserInfo user : mUserManager.getAliveUsers()) {
removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
synchronized (mSpManager) {
@@ -1128,6 +1152,9 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
setBoolean(MIGRATED_SP_FULL, true, 0);
+ if (isWeaverDisabledOnUnsecuredUsers()) {
+ setBoolean(MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS, true, 0);
+ }
}
mThirdPartyAppsStarted = true;
@@ -1151,13 +1178,61 @@ public class LockSettingsService extends ILockSettings.Stub {
getGateKeeperService(), protectorId, LockscreenCredential.createNone(), userId,
null);
SyntheticPassword sp = result.syntheticPassword;
- if (sp == null) {
+ if (isWeaverDisabledOnUnsecuredUsers()) {
+ Slog.i(TAG, "config_disableWeaverOnUnsecuredUsers=true");
+
+ // If config_disableWeaverOnUnsecuredUsers=true, then the Weaver HAL may be buggy and
+ // need multiple retries before it works here to unwrap the SP, if the SP was already
+ // protected by Weaver. Note that the problematic HAL can also deadlock if called with
+ // the ActivityManagerService lock held, but that should not be a problem here since
+ // that lock isn't held here, unlike unlockUserKeyIfUnsecured() where it is.
+ for (int i = 0; i < 12 && sp == null; i++) {
+ Slog.e(TAG, "Failed to unwrap synthetic password. Waiting 5 seconds to retry.");
+ SystemClock.sleep(5000);
+ result = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
+ LockscreenCredential.createNone(), userId, null);
+ sp = result.syntheticPassword;
+ }
+ if (sp == null) {
+ throw new IllegalStateException(
+ "Failed to unwrap synthetic password for unsecured user");
+ }
+ // If the SP is protected by Weaver, then remove the Weaver protection in order to make
+ // config_disableWeaverOnUnsecuredUsers=true take effect.
+ if (result.usedWeaver) {
+ Slog.i(TAG, "Removing Weaver protection from the synthetic password");
+ // Create a new protector, which will not use Weaver.
+ long newProtectorId = mSpManager.createLskfBasedProtector(
+ getGateKeeperService(), LockscreenCredential.createNone(), sp, userId);
+
+ // Out of paranoia, make sure the new protector really works.
+ result = mSpManager.unlockLskfBasedProtector(getGateKeeperService(),
+ newProtectorId, LockscreenCredential.createNone(), userId, null);
+ sp = result.syntheticPassword;
+ if (sp == null) {
+ throw new IllegalStateException("New SP protector does not work");
+ }
+
+ // Replace the protector. Wait until PHASE_BOOT_COMPLETED to destroy the old
+ // protector, since the Weaver slot erasure and freeing cannot be rolled back.
+ setCurrentLskfBasedProtectorId(newProtectorId, userId);
+ mProtectorsToDestroyOnBootCompleted.add(new Pair(protectorId, userId));
+ } else {
+ Slog.i(TAG, "Synthetic password is already not protected by Weaver");
+ }
+ } else if (sp == null) {
Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
return;
}
- // While setCeStorageProtection() is idempotent, it does log some error messages when called
- // again. Skip it if we know it was already handled by an earlier upgrade to Android 14.
- if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null) {
+
+ // Call setCeStorageProtection(), to re-encrypt the CE key with the SP if it's currently
+ // encrypted by an empty secret. Skip this if it was definitely already done as part of the
+ // upgrade to Android 14, since while setCeStorageProtection() is idempotent it does log
+ // some error messages when called again. Do not skip this if
+ // config_disableWeaverOnUnsecuredUsers=true, since in that case we'd like to recover from
+ // the case where an earlier upgrade to Android 14 incorrectly skipped this step.
+ if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null
+ || isWeaverDisabledOnUnsecuredUsers()) {
Slogf.i(TAG, "Encrypting CE key of user %d with synthetic password", userId);
setCeStorageProtection(userId, sp);
}
@@ -1165,6 +1240,17 @@ public class LockSettingsService extends ILockSettings.Stub {
initKeystoreSuperKeys(userId, sp, /* allowExisting= */ true);
}
+ private void destroyMigratedProtectors() {
+ if (!mProtectorsToDestroyOnBootCompleted.isEmpty()) {
+ synchronized (mSpManager) {
+ for (Pair<Long, Integer> pair : mProtectorsToDestroyOnBootCompleted) {
+ mSpManager.destroyLskfBasedProtector(pair.first, pair.second);
+ }
+ }
+ }
+ mProtectorsToDestroyOnBootCompleted = null; // The list is no longer needed.
+ }
+
/**
* Returns the lowest password quality that still presents the same UI for entering it.
*
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 3a429b041b3c..47788f2e7d2f 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -195,6 +195,8 @@ class SyntheticPasswordManager {
// ERROR: password / token fails verification
// RETRY: password / token verification is throttled at the moment.
@Nullable public VerifyCredentialResponse gkResponse;
+ // For unlockLskfBasedProtector() this is set to true if the protector uses Weaver.
+ public boolean usedWeaver;
}
/**
@@ -532,6 +534,11 @@ class SyntheticPasswordManager {
Settings.Global.DEVICE_PROVISIONED, 0) != 0;
}
+ private boolean isWeaverDisabledOnUnsecuredUsers() {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers);
+ }
+
@VisibleForTesting
protected android.hardware.weaver.V1_0.IWeaver getWeaverHidlService() throws RemoteException {
try {
@@ -1011,7 +1018,13 @@ class SyntheticPasswordManager {
Slogf.i(TAG, "Creating LSKF-based protector %016x for user %d", protectorId, userId);
- final IWeaver weaver = getWeaverService();
+ final IWeaver weaver;
+ if (credential.isNone() && isWeaverDisabledOnUnsecuredUsers()) {
+ weaver = null;
+ Slog.w(TAG, "Not using Weaver for unsecured user (disabled by config)");
+ } else {
+ weaver = getWeaverService();
+ }
if (weaver != null) {
// Weaver is available, so make the protector use it to verify the LSKF. Do this even
// if the LSKF is empty, as that gives us support for securely deleting the protector.
@@ -1404,6 +1417,7 @@ class SyntheticPasswordManager {
int weaverSlot = loadWeaverSlot(protectorId, userId);
if (weaverSlot != INVALID_WEAVER_SLOT) {
// Protector uses Weaver to verify the LSKF
+ result.usedWeaver = true;
final IWeaver weaver = getWeaverService();
if (weaver == null) {
Slog.e(TAG, "Protector uses Weaver, but Weaver is unavailable");
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 21ab7812e604..65f22416a535 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -743,6 +743,7 @@ public final class PowerManagerService extends SystemService
int reason, int uid, int opUid, String opPackageName, String details) {
mWakefulnessChanging = true;
mDirty |= DIRTY_WAKEFULNESS;
+ mInjector.invalidateIsInteractiveCaches();
if (wakefulness == WAKEFULNESS_AWAKE) {
// Kick user activity to prevent newly awake group from timing out instantly.
// The dream may end without user activity if the dream app crashes / is updated,
@@ -2035,7 +2036,7 @@ public final class PowerManagerService extends SystemService
}
@SuppressWarnings("deprecation")
- private boolean isWakeLockLevelSupportedInternal(int level) {
+ private boolean isWakeLockLevelSupportedInternal(int level, int displayId) {
synchronized (mLock) {
switch (level) {
case PowerManager.PARTIAL_WAKE_LOCK:
@@ -2047,7 +2048,8 @@ public final class PowerManagerService extends SystemService
return true;
case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
- return mSystemReady && mDisplayManagerInternal.isProximitySensorAvailable();
+ return mSystemReady
+ && mDisplayManagerInternal.isProximitySensorAvailable(displayId);
case PowerManager.SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK:
return mSystemReady && mFeatureFlags.isEarlyScreenTimeoutDetectorEnabled()
&& mScreenTimeoutOverridePolicy != null;
@@ -2264,7 +2266,6 @@ public final class PowerManagerService extends SystemService
int opUid, String opPackageName, String details) {
mPowerGroups.get(groupId).setWakefulnessLocked(wakefulness, eventTime, uid, reason, opUid,
opPackageName, details);
- mInjector.invalidateIsInteractiveCaches();
}
@SuppressWarnings("deprecation")
@@ -2329,8 +2330,6 @@ public final class PowerManagerService extends SystemService
Trace.traceBegin(Trace.TRACE_TAG_POWER, traceMethodName);
try {
// Phase 2: Handle wakefulness change and bookkeeping.
- // Under lock, invalidate before set ensures caches won't return stale values.
- mInjector.invalidateIsInteractiveCaches();
mWakefulnessRaw = newWakefulness;
mWakefulnessChanging = true;
mDirty |= DIRTY_WAKEFULNESS;
@@ -2428,6 +2427,7 @@ public final class PowerManagerService extends SystemService
void onPowerGroupEventLocked(int event, PowerGroup powerGroup) {
mWakefulnessChanging = true;
mDirty |= DIRTY_WAKEFULNESS;
+ mInjector.invalidateIsInteractiveCaches();
final int groupId = powerGroup.getGroupId();
if (event == DisplayGroupPowerChangeListener.DISPLAY_GROUP_REMOVED) {
mPowerGroups.delete(groupId);
@@ -3975,6 +3975,9 @@ public final class PowerManagerService extends SystemService
private boolean isInteractiveInternal(int displayId, int uid) {
synchronized (mLock) {
+ if (!mSystemReady) {
+ return isGloballyInteractiveInternal();
+ }
DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(displayId);
if (displayInfo == null) {
Slog.w(TAG, "Did not find DisplayInfo for displayId " + displayId);
@@ -5975,7 +5978,17 @@ public final class PowerManagerService extends SystemService
public boolean isWakeLockLevelSupported(int level) {
final long ident = Binder.clearCallingIdentity();
try {
- return isWakeLockLevelSupportedInternal(level);
+ return isWakeLockLevelSupportedInternal(level, Display.DEFAULT_DISPLAY);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public boolean isWakeLockLevelSupportedWithDisplayId(int level, int displayId) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return isWakeLockLevelSupportedInternal(level, displayId);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
index b5a7fcb72982..e650c52b68b4 100644
--- a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
@@ -56,24 +56,26 @@ final class ExternalVibrationSession extends Vibration
}
@Override
+ public long getCreateUptimeMillis() {
+ return stats.getCreateUptimeMillis();
+ }
+
+ @Override
public CallerInfo getCallerInfo() {
return callerInfo;
}
@Override
- public VibrationSession.DebugInfo getDebugInfo() {
- return new Vibration.DebugInfoImpl(getStatus(), stats, /* playedEffect= */ null,
- /* originalEffect= */ null, mScale.scaleLevel, mScale.adaptiveHapticsScale,
- callerInfo);
+ public IBinder getCallerToken() {
+ return mExternalVibration.getToken();
}
@Override
- public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
- return new VibrationStats.StatsInfo(
- mExternalVibration.getUid(),
- FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
- mExternalVibration.getVibrationAttributes().getUsage(), getStatus(), stats,
- completionUptimeMillis);
+ public VibrationSession.DebugInfo getDebugInfo() {
+ return new Vibration.DebugInfoImpl(getStatus(), callerInfo,
+ FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL, stats,
+ /* playedEffect= */ null, /* originalEffect= */ null, mScale.scaleLevel,
+ mScale.adaptiveHapticsScale);
}
@Override
@@ -86,6 +88,12 @@ final class ExternalVibrationSession extends Vibration
}
@Override
+ public boolean wasEndRequested() {
+ // End request is immediate, so just check if vibration has already ended.
+ return hasEnded();
+ }
+
+ @Override
public boolean linkToDeath(Runnable callback) {
synchronized (mLock) {
mBinderDeathCallback = callback;
@@ -104,10 +112,12 @@ final class ExternalVibrationSession extends Vibration
@Override
public void binderDied() {
+ Runnable callback;
synchronized (mLock) {
- if (mBinderDeathCallback != null) {
- mBinderDeathCallback.run();
- }
+ callback = mBinderDeathCallback;
+ }
+ if (callback != null) {
+ callback.run();
}
}
@@ -131,6 +141,16 @@ final class ExternalVibrationSession extends Vibration
end(new EndInfo(status, endedBy));
}
+ @Override
+ public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+ // ignored, external control does not expect callbacks from the vibrator
+ }
+
+ @Override
+ public void notifySyncedVibratorsCallback(long vibrationId) {
+ // ignored, external control does not expect callbacks from the vibrator manager
+ }
+
boolean isHoldingSameVibration(ExternalVibration vib) {
return mExternalVibration.equals(vib);
}
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
index ce9c47ba6ba4..fbcc856d0974 100644
--- a/services/core/java/com/android/server/vibrator/HalVibration.java
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -19,15 +19,16 @@ package com.android.server.vibrator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.CombinedVibration;
-import android.os.IBinder;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.util.SparseArray;
-import com.android.internal.util.FrameworkStatsLog;
-
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
+import java.util.function.IntFunction;
/**
* Represents a vibration defined by a {@link CombinedVibration} that will be performed by
@@ -36,7 +37,6 @@ import java.util.concurrent.CountDownLatch;
final class HalVibration extends Vibration {
public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
- public final IBinder callerToken;
/** A {@link CountDownLatch} to enable waiting for completion. */
private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
@@ -56,10 +56,9 @@ final class HalVibration extends Vibration {
private int mScaleLevel;
private float mAdaptiveScale;
- HalVibration(@NonNull IBinder callerToken, @NonNull CombinedVibration effect,
- @NonNull VibrationSession.CallerInfo callerInfo) {
+ HalVibration(@NonNull VibrationSession.CallerInfo callerInfo,
+ @NonNull CombinedVibration effect) {
super(callerInfo);
- this.callerToken = callerToken;
mOriginalEffect = effect;
mEffectToPlay = effect;
mScaleLevel = VibrationScaler.SCALE_NONE;
@@ -87,11 +86,11 @@ final class HalVibration extends Vibration {
}
/**
- * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
- * which might be necessary for replacement in realtime.
+ * Add a fallback {@link VibrationEffect} to be played for each predefined effect id, which
+ * might be necessary for replacement in realtime.
*/
- public void addFallback(int effectId, VibrationEffect effect) {
- mFallbacks.put(effectId, effect);
+ public void fillFallbacks(IntFunction<VibrationEffect> fallbackProvider) {
+ fillFallbacksForEffect(mEffectToPlay, fallbackProvider);
}
/**
@@ -131,11 +130,6 @@ final class HalVibration extends Vibration {
// No need to update fallback effects, they are already configured per device.
}
- @Override
- public boolean isRepeating() {
- return mOriginalEffect.getDuration() == Long.MAX_VALUE;
- }
-
/** Return the effect that should be played by this vibration. */
public CombinedVibration getEffectToPlay() {
return mEffectToPlay;
@@ -146,20 +140,9 @@ final class HalVibration extends Vibration {
// Clear the original effect if it's the same as the effect that was played, for simplicity
CombinedVibration originalEffect =
Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect;
- return new Vibration.DebugInfoImpl(getStatus(), stats, mEffectToPlay, originalEffect,
- mScaleLevel, mAdaptiveScale, callerInfo);
- }
-
- @Override
- public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
- int vibrationType = mEffectToPlay.hasVendorEffects()
- ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR
- : isRepeating()
- ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
- : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
- return new VibrationStats.StatsInfo(
- callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), getStatus(),
- stats, completionUptimeMillis);
+ return new Vibration.DebugInfoImpl(getStatus(), callerInfo,
+ VibrationStats.StatsInfo.findVibrationType(mEffectToPlay), stats, mEffectToPlay,
+ originalEffect, mScaleLevel, mAdaptiveScale);
}
/**
@@ -174,6 +157,42 @@ final class HalVibration extends Vibration {
return callerInfo.uid == vib.callerInfo.uid && callerInfo.attrs.isFlagSet(
VibrationAttributes.FLAG_PIPELINED_EFFECT)
&& vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
- && !isRepeating();
+ && (mOriginalEffect.getDuration() != Long.MAX_VALUE);
+ }
+
+ private void fillFallbacksForEffect(CombinedVibration effect,
+ IntFunction<VibrationEffect> fallbackProvider) {
+ if (effect instanceof CombinedVibration.Mono) {
+ fillFallbacksForEffect(((CombinedVibration.Mono) effect).getEffect(), fallbackProvider);
+ } else if (effect instanceof CombinedVibration.Stereo) {
+ SparseArray<VibrationEffect> effects =
+ ((CombinedVibration.Stereo) effect).getEffects();
+ for (int i = 0; i < effects.size(); i++) {
+ fillFallbacksForEffect(effects.valueAt(i), fallbackProvider);
+ }
+ } else if (effect instanceof CombinedVibration.Sequential) {
+ List<CombinedVibration> effects =
+ ((CombinedVibration.Sequential) effect).getEffects();
+ for (int i = 0; i < effects.size(); i++) {
+ fillFallbacksForEffect(effects.get(i), fallbackProvider);
+ }
+ }
+ }
+
+ private void fillFallbacksForEffect(VibrationEffect effect,
+ IntFunction<VibrationEffect> fallbackProvider) {
+ if (!(effect instanceof VibrationEffect.Composed composed)) {
+ return;
+ }
+ int segmentCount = composed.getSegments().size();
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = composed.getSegments().get(i);
+ if ((segment instanceof PrebakedSegment prebaked) && prebaked.shouldFallback()) {
+ VibrationEffect fallback = fallbackProvider.apply(prebaked.getEffectId());
+ if (fallback != null) {
+ mFallbacks.put(prebaked.getEffectId(), fallback);
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
new file mode 100644
index 000000000000..f80407d03e5c
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.NoSuchElementException;
+
+/**
+ * A vibration session holding a single {@link CombinedVibration} request, performed by a
+ * {@link VibrationStepConductor}.
+ */
+final class SingleVibrationSession implements VibrationSession, IBinder.DeathRecipient {
+ private static final String TAG = "SingleVibrationSession";
+
+ private final Object mLock = new Object();
+ private final IBinder mCallerToken;
+ private final HalVibration mVibration;
+
+ @GuardedBy("mLock")
+ private VibrationStepConductor mConductor;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private Runnable mBinderDeathCallback;
+
+ SingleVibrationSession(@NonNull IBinder callerToken, @NonNull CallerInfo callerInfo,
+ @NonNull CombinedVibration vibration) {
+ mCallerToken = callerToken;
+ mVibration = new HalVibration(callerInfo, vibration);
+ }
+
+ public void setVibrationConductor(@Nullable VibrationStepConductor conductor) {
+ synchronized (mLock) {
+ mConductor = conductor;
+ }
+ }
+
+ public HalVibration getVibration() {
+ return mVibration;
+ }
+
+ @Override
+ public long getCreateUptimeMillis() {
+ return mVibration.stats.getCreateUptimeMillis();
+ }
+
+ @Override
+ public boolean isRepeating() {
+ return mVibration.getEffectToPlay().getDuration() == Long.MAX_VALUE;
+ }
+
+ @Override
+ public CallerInfo getCallerInfo() {
+ return mVibration.callerInfo;
+ }
+
+ @Override
+ public IBinder getCallerToken() {
+ return mCallerToken;
+ }
+
+ @Override
+ public DebugInfo getDebugInfo() {
+ return mVibration.getDebugInfo();
+ }
+
+ @Override
+ public boolean wasEndRequested() {
+ if (mVibration.hasEnded()) {
+ return true;
+ }
+ synchronized (mLock) {
+ return mConductor != null && mConductor.wasNotifiedToCancel();
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ Slog.d(TAG, "Binder died, cancelling vibration...");
+ requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false);
+ Runnable callback;
+ synchronized (mLock) {
+ callback = mBinderDeathCallback;
+ }
+ if (callback != null) {
+ callback.run();
+ }
+ }
+
+ @Override
+ public boolean linkToDeath(@Nullable Runnable callback) {
+ synchronized (mLock) {
+ mBinderDeathCallback = callback;
+ }
+ try {
+ mCallerToken.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error linking vibration to token death", e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void unlinkToDeath() {
+ try {
+ mCallerToken.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Slog.wtf(TAG, "Failed to unlink vibration to token death", e);
+ }
+ synchronized (mLock) {
+ mBinderDeathCallback = null;
+ }
+ }
+
+ @Override
+ public void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy,
+ boolean immediate) {
+ synchronized (mLock) {
+ if (mConductor != null) {
+ mConductor.notifyCancelled(new Vibration.EndInfo(status, endedBy), immediate);
+ } else {
+ mVibration.end(new Vibration.EndInfo(status, endedBy));
+ }
+ }
+ }
+
+ @Override
+ public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+ if (vibrationId != mVibration.id) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mConductor != null) {
+ mConductor.notifyVibratorComplete(vibratorId);
+ }
+ }
+ }
+
+ @Override
+ public void notifySyncedVibratorsCallback(long vibrationId) {
+ if (vibrationId != mVibration.id) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mConductor != null) {
+ mConductor.notifySyncedVibrationComplete();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 21fd4ce0acd0..bb2a17c698ee 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -88,15 +88,9 @@ abstract class Vibration {
stats.reportEnded(endInfo.endedBy);
}
- /** Return true if vibration is a repeating vibration. */
- abstract boolean isRepeating();
-
/** Return {@link VibrationSession.DebugInfo} with read-only debug data about this vibration. */
abstract VibrationSession.DebugInfo getDebugInfo();
- /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
- abstract VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis);
-
/** Immutable info passed as a signal to end a vibration. */
static final class EndInfo {
/** The vibration status to be set when it ends with this info. */
@@ -146,35 +140,41 @@ abstract class Vibration {
* potentially expensive or resource-linked objects, such as {@link IBinder}.
*/
static final class DebugInfoImpl implements VibrationSession.DebugInfo {
- final VibrationSession.Status mStatus;
- final long mCreateTime;
- final VibrationSession.CallerInfo mCallerInfo;
+ private final VibrationSession.Status mStatus;
+ private final VibrationStats.StatsInfo mStatsInfo;
+ private final VibrationSession.CallerInfo mCallerInfo;
@Nullable
- final CombinedVibration mPlayedEffect;
-
- private final long mStartTime;
- private final long mEndTime;
- private final long mDurationMs;
+ private final CombinedVibration mPlayedEffect;
@Nullable
private final CombinedVibration mOriginalEffect;
private final int mScaleLevel;
private final float mAdaptiveScale;
- DebugInfoImpl(VibrationSession.Status status, VibrationStats stats,
- @Nullable CombinedVibration playedEffect,
- @Nullable CombinedVibration originalEffect, int scaleLevel,
- float adaptiveScale, @NonNull VibrationSession.CallerInfo callerInfo) {
+ private final long mCreateUptime;
+ private final long mCreateTime;
+ private final long mStartTime;
+ private final long mEndTime;
+ private final long mDurationMs;
+
+ DebugInfoImpl(VibrationSession.Status status,
+ @NonNull VibrationSession.CallerInfo callerInfo, int vibrationType,
+ VibrationStats stats, @Nullable CombinedVibration playedEffect,
+ @Nullable CombinedVibration originalEffect, int scaleLevel, float adaptiveScale) {
Objects.requireNonNull(callerInfo);
- mCreateTime = stats.getCreateTimeDebug();
- mStartTime = stats.getStartTimeDebug();
- mEndTime = stats.getEndTimeDebug();
- mDurationMs = stats.getDurationDebug();
+ mCallerInfo = callerInfo;
+ mStatsInfo = stats.toStatsInfo(callerInfo.uid, vibrationType,
+ callerInfo.attrs.getUsage(), status);
mPlayedEffect = playedEffect;
mOriginalEffect = originalEffect;
mScaleLevel = scaleLevel;
mAdaptiveScale = adaptiveScale;
- mCallerInfo = callerInfo;
mStatus = status;
+
+ mCreateUptime = stats.getCreateUptimeMillis();
+ mCreateTime = stats.getCreateTimeDebug();
+ mStartTime = stats.getStartTimeDebug();
+ mEndTime = stats.getEndTimeDebug();
+ mDurationMs = stats.getDurationDebug();
}
@Override
@@ -184,7 +184,7 @@ abstract class Vibration {
@Override
public long getCreateUptimeMillis() {
- return mCreateTime;
+ return mCreateUptime;
}
@Override
@@ -216,6 +216,7 @@ abstract class Vibration {
@Override
public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
+ statsLogger.writeVibrationReportedAsync(mStatsInfo);
}
@Override
diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java
index 70477a26b759..4de8f78f7836 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSession.java
@@ -39,9 +39,18 @@ import java.util.Objects;
*/
interface VibrationSession {
+ /** Returns the session creation time from {@link android.os.SystemClock#uptimeMillis()}. */
+ long getCreateUptimeMillis();
+
+ /** Return true if vibration session plays a repeating vibration. */
+ boolean isRepeating();
+
/** Returns data about the client app that triggered this vibration session. */
CallerInfo getCallerInfo();
+ /** Returns the binder token from the client app attached to this vibration session. */
+ IBinder getCallerToken();
+
/** Returns debug data for logging and metric reports. */
DebugInfo getDebugInfo();
@@ -58,6 +67,19 @@ interface VibrationSession {
/** Removes link to the app process death. */
void unlinkToDeath();
+ /** Returns true if this session was requested to end by {@link #requestEnd}. */
+ boolean wasEndRequested();
+
+ /**
+ * Request the end of this session, which might be acted upon asynchronously.
+ *
+ * <p>This is the same as {@link #requestEnd(Status, CallerInfo, boolean)}, with no
+ * {@link CallerInfo} and with {@code immediate} flag set to false.
+ */
+ default void requestEnd(@NonNull Status status) {
+ requestEnd(status, /* endedBy= */ null, /* immediate= */ false);
+ }
+
/**
* Notify the session end was requested, which might be acted upon asynchronously.
*
@@ -71,6 +93,25 @@ interface VibrationSession {
void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy, boolean immediate);
/**
+ * Notify a vibrator has completed the last command during the playback of given vibration.
+ *
+ * <p>This will be called by the vibrator hardware callback indicating the last vibrate call is
+ * complete (e.g. on(), perform(), compose()). This does not mean the vibration is complete,
+ * since its playback might have one or more interactions with the vibrator hardware.
+ */
+ void notifyVibratorCallback(int vibratorId, long vibrationId);
+
+ /**
+ * Notify all synced vibrators have completed the last synchronized command during the playback
+ * of given vibration.
+ *
+ * <p>This will be called by the vibrator manager hardware callback indicating the last
+ * synchronized vibrate call is complete. This does not mean the vibration is complete, since
+ * its playback might have one or more interactions with the vibrator hardware.
+ */
+ void notifySyncedVibratorsCallback(long vibrationId);
+
+ /**
* Session status with reference to values from vibratormanagerservice.proto for logging.
*/
enum Status {
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
index fc0c6e7bf05e..637a5a180063 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStats.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -18,6 +18,7 @@ package com.android.server.vibrator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.CombinedVibration;
import android.os.SystemClock;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
@@ -37,11 +38,11 @@ final class VibrationStats {
// vibrate request.
// - Start: time a vibration started to play, which is closer to the time that the
// VibrationEffect started playing the very first segment.
- // - End: time a vibration ended, even if it never started to play. This can be as soon as the
- // vibrator HAL reports it has finished the last command, or before it has even started
- // when the vibration is ignored or cancelled.
- // Create and end times set by VibratorManagerService only, guarded by its lock.
- // Start times set by VibrationThread only (single-threaded).
+ // - End: time a vibration ended with a status, even if it never started to play. This can be as
+ // soon as the vibrator HAL reports it has finished the last command, or before it has
+ // even started when the vibration is ignored or cancelled.
+ // Created and ended times set by VibratorManagerService only, guarded by its lock.
+ // Start time set by VibrationThread only (single-threaded).
private long mCreateUptimeMillis;
private long mStartUptimeMillis;
private long mEndUptimeMillis;
@@ -97,6 +98,10 @@ final class VibrationStats {
mInterruptedUsage = -1;
}
+ StatsInfo toStatsInfo(int uid, int vibrationType, int usage, VibrationSession.Status status) {
+ return new VibrationStats.StatsInfo(uid, vibrationType, usage, status, this);
+ }
+
long getCreateUptimeMillis() {
return mCreateUptimeMillis;
}
@@ -300,7 +305,7 @@ final class VibrationStats {
* {@link com.android.internal.util.FrameworkStatsLog} as a
* {@link com.android.internal.util.FrameworkStatsLog#VIBRATION_REPORTED}.
*/
- static final class StatsInfo {
+ public static final class StatsInfo {
public final int uid;
public final int vibrationType;
public final int usage;
@@ -331,7 +336,7 @@ final class VibrationStats {
private boolean mIsWritten;
StatsInfo(int uid, int vibrationType, int usage, VibrationSession.Status status,
- VibrationStats stats, long completionUptimeMillis) {
+ VibrationStats stats) {
this.uid = uid;
this.vibrationType = vibrationType;
this.usage = usage;
@@ -342,6 +347,9 @@ final class VibrationStats {
interruptedUsage = stats.mInterruptedUsage;
repeatCount = stats.mRepeatCount;
+ // Consider this vibration is being completed now.
+ long completionUptimeMillis = SystemClock.uptimeMillis();
+
// This duration goes from the time this object was created until the time it was
// completed. We can use latencies to detect the times between first and last
// interaction with vibrator.
@@ -419,5 +427,25 @@ final class VibrationStats {
}
return res;
}
+
+ /**
+ * Returns the vibration type value from {@code ReportedVibration} that best represents this
+ * {@link CombinedVibration}.
+ *
+ * <p>This does not include external vibrations, as those are not represented by a single
+ * vibration effect.
+ */
+ public static int findVibrationType(@Nullable CombinedVibration effect) {
+ if (effect == null) {
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+ }
+ if (effect.hasVendorEffects()) {
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR;
+ }
+ if (effect.getDuration() == Long.MAX_VALUE) {
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED;
+ }
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+ }
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 1d52e3c87d17..4bb0c16d9655 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -20,8 +20,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Build;
import android.os.CombinedVibration;
-import android.os.IBinder;
-import android.os.RemoteException;
import android.os.VibrationEffect;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
@@ -39,7 +37,6 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
-import java.util.NoSuchElementException;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.CancellationException;
@@ -55,7 +52,7 @@ import java.util.concurrent.TimeoutException;
* VibrationThread. The only thread-safe methods for calling from other threads are the "notify"
* methods (which should never be used from the VibrationThread thread).
*/
-final class VibrationStepConductor implements IBinder.DeathRecipient {
+final class VibrationStepConductor {
private static final boolean DEBUG = VibrationThread.DEBUG;
private static final String TAG = VibrationThread.TAG;
@@ -346,42 +343,6 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
}
/**
- * Binder death notification. VibrationThread registers this when it's running a conductor.
- * Note that cancellation could theoretically happen immediately, before the conductor has
- * started, but in this case it will be processed in the first signals loop.
- */
- @Override
- public void binderDied() {
- if (DEBUG) {
- Slog.d(TAG, "Binder died, cancelling vibration...");
- }
- notifyCancelled(new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED),
- /* immediate= */ false);
- }
-
- /**
- * Returns true if successfully linked this conductor to the death of the binder that requested
- * the vibration.
- */
- public boolean linkToDeath() {
- try {
- mVibration.callerToken.linkToDeath(this, 0);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error linking vibration to token death", e);
- return false;
- }
- return true;
- }
-
- public void unlinkToDeath() {
- try {
- mVibration.callerToken.unlinkToDeath(this, 0);
- } catch (NoSuchElementException e) {
- Slog.wtf(TAG, "Failed to unlink vibration to token death", e);
- }
- }
-
- /**
* Notify the execution that cancellation is requested. This will be acted upon
* asynchronously in the VibrationThread.
*
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 07473d10b217..9b7bdece69f9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -51,7 +51,6 @@ import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
-import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
@@ -159,9 +158,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@GuardedBy("mLock")
private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
@GuardedBy("mLock")
- private VibrationStepConductor mCurrentVibration;
+ private SingleVibrationSession mCurrentVibration;
@GuardedBy("mLock")
- private VibrationStepConductor mNextVibration;
+ private SingleVibrationSession mNextVibration;
@GuardedBy("mLock")
private ExternalVibrationSession mCurrentExternalVibration;
@GuardedBy("mLock")
@@ -188,24 +187,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// When the system is entering a non-interactive state, we want to cancel
// vibrations in case a misbehaving app has abandoned them.
if (shouldCancelOnScreenOffLocked(mNextVibration)) {
- clearNextVibrationLocked(new Vibration.EndInfo(
- Status.CANCELLED_BY_SCREEN_OFF));
+ clearNextVibrationLocked(Status.CANCELLED_BY_SCREEN_OFF);
}
if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
- Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ false);
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_SCREEN_OFF);
}
}
} else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()
&& intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) {
synchronized (mLock) {
if (shouldCancelOnFgUserRequest(mNextVibration)) {
- clearNextVibrationLocked(new Vibration.EndInfo(
- Status.CANCELLED_BY_FOREGROUND_USER));
+ clearNextVibrationLocked(Status.CANCELLED_BY_FOREGROUND_USER);
}
if (shouldCancelOnFgUserRequest(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
- Status.CANCELLED_BY_FOREGROUND_USER), /* immediate= */ false);
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_FOREGROUND_USER);
}
}
}
@@ -222,12 +217,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
synchronized (mLock) {
if (shouldCancelAppOpModeChangedLocked(mNextVibration)) {
- clearNextVibrationLocked(new Vibration.EndInfo(
- Status.CANCELLED_BY_APP_OPS));
+ clearNextVibrationLocked(Status.CANCELLED_BY_APP_OPS);
}
if (shouldCancelAppOpModeChangedLocked(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
- Status.CANCELLED_BY_APP_OPS), /* immediate= */ false);
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_APP_OPS);
}
}
}
@@ -602,8 +595,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return null;
}
// Create Vibration.Stats as close to the received request as possible, for tracking.
- HalVibration vib = new HalVibration(token, effect, callerInfo);
- fillVibrationFallbacks(vib, effect);
+ SingleVibrationSession session = new SingleVibrationSession(token, callerInfo, effect);
+ HalVibration vib = session.getVibration();
+ vib.fillFallbacks(mVibrationSettings::getFallbackEffect);
if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
// Force update of user settings before checking if this vibration effect should
@@ -617,21 +611,26 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
// Check if user settings or DnD is set to ignore this vibration.
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
+ Status ignoreStatus = shouldIgnoreVibrationLocked(callerInfo);
+ CallerInfo ignoredBy = null;
// Check if ongoing vibration is more important than this vibration.
- if (vibrationEndInfo == null) {
- vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib);
+ if (ignoreStatus == null) {
+ Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(session);
+ if (vibrationEndInfo != null) {
+ ignoreStatus = vibrationEndInfo.status;
+ ignoredBy = vibrationEndInfo.endedBy;
+ }
}
// If not ignored so far then try to start this vibration.
- if (vibrationEndInfo == null) {
+ if (ignoreStatus == null) {
final long ident = Binder.clearCallingIdentity();
try {
if (mCurrentExternalVibration != null) {
vib.stats.reportInterruptedAnotherVibration(
mCurrentExternalVibration.getCallerInfo());
- endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED, vib.callerInfo,
+ endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED, callerInfo,
/* continueExternalControl= */ false);
} else if (mCurrentVibration != null) {
if (mCurrentVibration.getVibration().canPipelineWith(vib)) {
@@ -645,21 +644,19 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
} else {
vib.stats.reportInterruptedAnotherVibration(
mCurrentVibration.getVibration().callerInfo);
- mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
- vib.callerInfo),
+ mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo,
/* immediate= */ false);
}
}
- vibrationEndInfo = startVibrationLocked(vib);
+ ignoreStatus = startVibrationLocked(session);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
// Ignored or failed to start the vibration, end it and report metrics right away.
- if (vibrationEndInfo != null) {
- endVibrationLocked(vib, vibrationEndInfo);
+ if (ignoreStatus != null) {
+ endVibrationLocked(session, ignoreStatus, ignoredBy);
}
return vib;
}
@@ -677,27 +674,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (DEBUG) {
Slog.d(TAG, "Canceling vibration");
}
- Vibration.EndInfo cancelledByUserInfo =
- new Vibration.EndInfo(Status.CANCELLED_BY_USER);
final long ident = Binder.clearCallingIdentity();
try {
- if (mNextVibration != null
- && shouldCancelVibration(mNextVibration.getVibration(),
- usageFilter, token)) {
- clearNextVibrationLocked(cancelledByUserInfo);
+ if (shouldCancelVibration(mNextVibration, usageFilter, token)) {
+ clearNextVibrationLocked(Status.CANCELLED_BY_USER);
}
- if (mCurrentVibration != null
- && shouldCancelVibration(mCurrentVibration.getVibration(),
- usageFilter, token)) {
- mCurrentVibration.notifyCancelled(
- cancelledByUserInfo, /* immediate= */false);
+ if (shouldCancelVibration(mCurrentVibration, usageFilter, token)) {
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_USER);
}
- if (mCurrentExternalVibration != null
- && shouldCancelVibration(
- mCurrentExternalVibration.getCallerInfo().attrs,
- usageFilter)) {
- endExternalVibrateLocked(cancelledByUserInfo.status,
- cancelledByUserInfo.endedBy, /* continueExternalControl= */ false);
+ // TODO(b/370948466): investigate why token is not checked here and fix it.
+ if (shouldCancelVibration(mCurrentExternalVibration, usageFilter, null)) {
+ endExternalVibrateLocked(Status.CANCELLED_BY_USER,
+ /* endedBy= */ null, /* continueExternalControl= */ false);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -842,18 +830,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return;
}
- HalVibration vib = mCurrentVibration.getVibration();
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
-
- if (inputDevicesChanged || (vibrationEndInfo != null)) {
+ Status ignoreStatus = shouldIgnoreVibrationLocked(mCurrentVibration.getCallerInfo());
+ if (inputDevicesChanged || (ignoreStatus != null)) {
if (DEBUG) {
Slog.d(TAG, "Canceling vibration because settings changed: "
- + (inputDevicesChanged ? "input devices changed"
- : vibrationEndInfo.status));
+ + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
}
- mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE),
- /* immediate= */ false);
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_SETTINGS_UPDATE);
}
}
}
@@ -873,8 +856,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (vibrator == null) {
continue;
}
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
- if (vibrationEndInfo == null) {
+ Status ignoreStatus = shouldIgnoreVibrationLocked(vib.callerInfo);
+ if (ignoreStatus == null) {
effect = mVibrationScaler.scale(effect, vib.callerInfo.attrs.getUsage());
} else {
// Vibration should not run, use null effect to remove registered effect.
@@ -886,25 +869,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo startVibrationLocked(HalVibration vib) {
+ private Status startVibrationLocked(SingleVibrationSession session) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
if (mInputDeviceDelegate.isAvailable()) {
- return startVibrationOnInputDevicesLocked(vib);
+ return startVibrationOnInputDevicesLocked(session.getVibration());
}
-
- VibrationStepConductor conductor = createVibrationStepConductor(vib);
-
if (mCurrentVibration == null) {
- return startVibrationOnThreadLocked(conductor);
+ return startVibrationOnThreadLocked(session);
}
// If there's already a vibration queued (waiting for the previous one to finish
// cancelling), end it cleanly and replace it with the new one.
// Note that we don't consider pipelining here, because new pipelined ones should
// replace pending non-executing pipelined ones anyway.
- clearNextVibrationLocked(
- new Vibration.EndInfo(Status.IGNORED_SUPERSEDED, vib.callerInfo));
- mNextVibration = conductor;
+ clearNextVibrationLocked(Status.IGNORED_SUPERSEDED, session.getCallerInfo());
+ mNextVibration = session;
return null;
} finally {
Trace.traceEnd(TRACE_TAG_VIBRATOR);
@@ -913,50 +892,45 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo startVibrationOnThreadLocked(VibrationStepConductor conductor) {
- HalVibration vib = conductor.getVibration();
- int mode = startAppOpModeLocked(vib.callerInfo);
+ private Status startVibrationOnThreadLocked(SingleVibrationSession session) {
+ VibrationStepConductor conductor = createVibrationStepConductor(session.getVibration());
+ session.setVibrationConductor(conductor);
+ int mode = startAppOpModeLocked(session.getCallerInfo());
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0);
// Make sure mCurrentVibration is set while triggering the VibrationThread.
- mCurrentVibration = conductor;
- if (!mCurrentVibration.linkToDeath()) {
+ mCurrentVibration = session;
+ if (!mCurrentVibration.linkToDeath(null)) {
// Shouldn't happen. The method call already logs a wtf.
mCurrentVibration = null; // Aborted.
- return new Vibration.EndInfo(Status.IGNORED_ERROR_TOKEN);
+ return Status.IGNORED_ERROR_TOKEN;
}
- if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
+ if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) {
// Shouldn't happen. The method call already logs a wtf.
+ mCurrentVibration.setVibrationConductor(null);
mCurrentVibration = null; // Aborted.
- return new Vibration.EndInfo(Status.IGNORED_ERROR_SCHEDULING);
+ return Status.IGNORED_ERROR_SCHEDULING;
}
return null;
case AppOpsManager.MODE_ERRORED:
Slog.w(TAG, "Start AppOpsManager operation errored for uid "
- + vib.callerInfo.uid);
- return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
+ + session.getCallerInfo().uid);
+ return Status.IGNORED_ERROR_APP_OPS;
default:
- return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
+ return Status.IGNORED_APP_OPS;
}
}
@GuardedBy("mLock")
- private void endVibrationLocked(Vibration vib, Status status) {
- endVibrationLocked(vib, new Vibration.EndInfo(status));
- }
-
- @GuardedBy("mLock")
- private void endVibrationLocked(Vibration vib, Vibration.EndInfo vibrationEndInfo) {
- vib.end(vibrationEndInfo);
- reportEndedVibrationLocked(vib);
+ private void endVibrationLocked(VibrationSession session, Status status) {
+ endVibrationLocked(session, status, /* endedBy= */ null);
}
@GuardedBy("mLock")
- private void reportEndedVibrationLocked(Vibration vib) {
- logAndRecordVibration(vib.getDebugInfo());
- mFrameworkStatsLogger.writeVibrationReportedAsync(
- vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
+ private void endVibrationLocked(VibrationSession session, Status status, CallerInfo endedBy) {
+ session.requestEnd(status, endedBy, /* immediate= */ false);
+ logAndRecordVibration(session.getDebugInfo());
}
private VibrationStepConductor createVibrationStepConductor(HalVibration vib) {
@@ -975,12 +949,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mFrameworkStatsLogger, requestVibrationParamsFuture, mVibrationThreadCallbacks);
}
- private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
+ private Status startVibrationOnInputDevicesLocked(HalVibration vib) {
// Scale resolves the default amplitudes from the effect before scaling them.
vib.scaleEffects(mVibrationScaler);
mInputDeviceDelegate.vibrateIfAvailable(vib.callerInfo, vib.getEffectToPlay());
-
- return new Vibration.EndInfo(Status.FORWARDED_TO_INPUT_DEVICES);
+ return Status.FORWARDED_TO_INPUT_DEVICES;
}
private void logAndRecordPerformHapticFeedbackAttempt(int uid, int deviceId, String opPkg,
@@ -994,9 +967,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private void logAndRecordVibrationAttempt(@Nullable CombinedVibration effect,
CallerInfo callerInfo, Status status) {
logAndRecordVibration(
- new Vibration.DebugInfoImpl(status, new VibrationStats(),
+ new Vibration.DebugInfoImpl(status, callerInfo,
+ VibrationStats.StatsInfo.findVibrationType(effect), new VibrationStats(),
effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE,
- VibrationScaler.ADAPTIVE_SCALE_NONE, callerInfo));
+ VibrationScaler.ADAPTIVE_SCALE_NONE));
}
private void logAndRecordVibration(DebugInfo info) {
@@ -1050,39 +1024,25 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
- @GuardedBy("mLock")
- private void reportFinishedVibrationLocked() {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
- mCurrentVibration.unlinkToDeath();
- HalVibration vib = mCurrentVibration.getVibration();
- if (DEBUG) {
- Slog.d(TAG, "Reporting vibration " + vib.id + " finished with " + vib.getStatus());
- }
- finishAppOpModeLocked(vib.callerInfo);
- reportEndedVibrationLocked(vib);
- }
-
private void onSyncedVibrationComplete(long vibrationId) {
synchronized (mLock) {
- if (mCurrentVibration != null
- && mCurrentVibration.getVibration().id == vibrationId) {
+ if (mCurrentVibration != null) {
if (DEBUG) {
Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread");
}
- mCurrentVibration.notifySyncedVibrationComplete();
+ mCurrentVibration.notifySyncedVibratorsCallback(vibrationId);
}
}
}
private void onVibrationComplete(int vibratorId, long vibrationId) {
synchronized (mLock) {
- if (mCurrentVibration != null
- && mCurrentVibration.getVibration().id == vibrationId) {
+ if (mCurrentVibration != null) {
if (DEBUG) {
Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
+ " complete, notifying thread");
}
- mCurrentVibration.notifyVibratorComplete(vibratorId);
+ mCurrentVibration.notifyVibratorCallback(vibratorId, vibrationId);
}
}
}
@@ -1094,14 +1054,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
*/
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(Vibration vib) {
+ private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(VibrationSession session) {
if (mCurrentExternalVibration != null) {
- return shouldIgnoreVibrationForOngoing(vib, mCurrentExternalVibration);
+ return shouldIgnoreVibrationForOngoing(session, mCurrentExternalVibration);
}
if (mNextVibration != null) {
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(vib,
- mNextVibration.getVibration());
+ Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(session,
+ mNextVibration);
if (vibrationEndInfo != null) {
// Next vibration has higher importance than the new one, so the new vibration
// should be ignored.
@@ -1110,14 +1070,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
if (mCurrentVibration != null) {
- HalVibration currentVibration = mCurrentVibration.getVibration();
- if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) {
- // Current vibration has ended or is cancelling, should not block incoming
- // vibrations.
+ if (mCurrentVibration.wasEndRequested()) {
+ // Current session has ended or is cancelling, should not block incoming vibrations.
return null;
}
- return shouldIgnoreVibrationForOngoing(vib, currentVibration);
+ return shouldIgnoreVibrationForOngoing(session, mCurrentVibration);
}
return null;
@@ -1125,32 +1083,33 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/**
* Checks if the ongoing vibration has higher importance than the new one. If they have similar
- * importance, then {@link Vibration#isRepeating()} is used as a tiebreaker.
+ * importance, then {@link VibrationSession#isRepeating()} is used as a tiebreaker.
*
* @return a Vibration.EndInfo if the vibration should be ignored, null otherwise.
*/
@Nullable
private static Vibration.EndInfo shouldIgnoreVibrationForOngoing(
- @NonNull Vibration newVibration, @NonNull Vibration ongoingVibration) {
+ @NonNull VibrationSession newSession, @NonNull VibrationSession ongoingSession) {
- int newVibrationImportance = getVibrationImportance(newVibration);
- int ongoingVibrationImportance = getVibrationImportance(ongoingVibration);
+ int newSessionImportance = getVibrationImportance(newSession);
+ int ongoingSessionImportance = getVibrationImportance(ongoingSession);
- if (newVibrationImportance > ongoingVibrationImportance) {
+ if (newSessionImportance > ongoingSessionImportance) {
// New vibration has higher importance and should not be ignored.
return null;
}
- if (ongoingVibrationImportance > newVibrationImportance) {
+ if (ongoingSessionImportance > newSessionImportance) {
// Existing vibration has higher importance and should not be cancelled.
return new Vibration.EndInfo(Status.IGNORED_FOR_HIGHER_IMPORTANCE,
- ongoingVibration.callerInfo);
+ ongoingSession.getCallerInfo());
}
// Same importance, use repeating as a tiebreaker.
- if (ongoingVibration.isRepeating() && !newVibration.isRepeating()) {
+ if (ongoingSession.isRepeating() && !newSession.isRepeating()) {
// Ongoing vibration is repeating and new one is not, give priority to ongoing
- return new Vibration.EndInfo(Status.IGNORED_FOR_ONGOING, ongoingVibration.callerInfo);
+ return new Vibration.EndInfo(Status.IGNORED_FOR_ONGOING,
+ ongoingSession.getCallerInfo());
}
// New vibration is repeating or this is a complete tie between them,
// give priority to new vibration.
@@ -1164,10 +1123,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
* @return a numeric representation for the vibration importance, larger values represent a
* higher importance
*/
- private static int getVibrationImportance(Vibration vibration) {
- int usage = vibration.callerInfo.attrs.getUsage();
+ private static int getVibrationImportance(VibrationSession session) {
+ int usage = session.getCallerInfo().attrs.getUsage();
if (usage == VibrationAttributes.USAGE_UNKNOWN) {
- if (vibration.isRepeating()) {
+ if (session.isRepeating()) {
usage = VibrationAttributes.USAGE_RINGTONE;
} else {
usage = VibrationAttributes.USAGE_TOUCH;
@@ -1201,10 +1160,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
*/
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo shouldIgnoreVibrationLocked(CallerInfo callerInfo) {
+ private Status shouldIgnoreVibrationLocked(CallerInfo callerInfo) {
Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(callerInfo);
if (statusFromSettings != null) {
- return new Vibration.EndInfo(statusFromSettings);
+ return statusFromSettings;
}
int mode = checkAppOpModeLocked(callerInfo);
@@ -1212,9 +1171,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (mode == AppOpsManager.MODE_ERRORED) {
// We might be getting calls from within system_server, so we don't actually
// want to throw a SecurityException here.
- return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
+ return Status.IGNORED_ERROR_APP_OPS;
} else {
- return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
+ return Status.IGNORED_APP_OPS;
}
}
@@ -1239,32 +1198,29 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/**
* Return true if the vibration has the same token and usage belongs to given usage class.
*
- * @param vib The ongoing or pending vibration to be cancelled.
+ * @param session The ongoing or pending vibration session to be cancelled.
* @param usageFilter The vibration usages to be cancelled, any bitwise combination of
* VibrationAttributes.USAGE_* values.
- * @param token The binder token to identify the vibration origin. Only vibrations
+ * @param tokenFilter The binder token to identify the vibration origin. Only vibrations
* started with the same token can be cancelled with it.
*/
- private boolean shouldCancelVibration(HalVibration vib, int usageFilter, IBinder token) {
- return (vib.callerToken == token) && shouldCancelVibration(vib.callerInfo.attrs,
- usageFilter);
- }
-
- /**
- * Return true if the external vibration usage belongs to given usage class.
- *
- * @param attrs The attributes of an ongoing or pending vibration to be cancelled.
- * @param usageFilter The vibration usages to be cancelled, any bitwise combination of
- * VibrationAttributes.USAGE_* values.
- */
- private boolean shouldCancelVibration(VibrationAttributes attrs, int usageFilter) {
- if (attrs.getUsage() == VibrationAttributes.USAGE_UNKNOWN) {
+ private boolean shouldCancelVibration(@Nullable VibrationSession session, int usageFilter,
+ @Nullable IBinder tokenFilter) {
+ if (session == null) {
+ return false;
+ }
+ if ((tokenFilter != null) && (tokenFilter != session.getCallerToken())) {
+ // Vibration from a different app, this should not cancel it.
+ return false;
+ }
+ int usage = session.getCallerInfo().attrs.getUsage();
+ if (usage == VibrationAttributes.USAGE_UNKNOWN) {
// Special case, usage UNKNOWN would match all filters. Instead it should only match if
// it's cancelling that usage specifically, or if cancelling all usages.
return usageFilter == VibrationAttributes.USAGE_UNKNOWN
|| usageFilter == VibrationAttributes.USAGE_FILTER_MATCH_ALL;
}
- return (usageFilter & attrs.getUsage()) == attrs.getUsage();
+ return (usageFilter & usage) == usage;
}
/**
@@ -1340,45 +1296,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
/**
- * Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
- * VibrationSettings#getFallbackEffect}.
- */
- private void fillVibrationFallbacks(HalVibration vib, CombinedVibration effect) {
- if (effect instanceof CombinedVibration.Mono) {
- fillVibrationFallbacks(vib, ((CombinedVibration.Mono) effect).getEffect());
- } else if (effect instanceof CombinedVibration.Stereo) {
- SparseArray<VibrationEffect> effects =
- ((CombinedVibration.Stereo) effect).getEffects();
- for (int i = 0; i < effects.size(); i++) {
- fillVibrationFallbacks(vib, effects.valueAt(i));
- }
- } else if (effect instanceof CombinedVibration.Sequential) {
- List<CombinedVibration> effects =
- ((CombinedVibration.Sequential) effect).getEffects();
- for (int i = 0; i < effects.size(); i++) {
- fillVibrationFallbacks(vib, effects.get(i));
- }
- }
- }
-
- private void fillVibrationFallbacks(HalVibration vib, VibrationEffect effect) {
- if (!(effect instanceof VibrationEffect.Composed composed)) {
- return;
- }
- int segmentCount = composed.getSegments().size();
- for (int i = 0; i < segmentCount; i++) {
- VibrationEffectSegment segment = composed.getSegments().get(i);
- if (segment instanceof PrebakedSegment prebaked) {
- VibrationEffect fallback = mVibrationSettings.getFallbackEffect(
- prebaked.getEffectId());
- if (prebaked.shouldFallback() && fallback != null) {
- vib.addFallback(prebaked.getEffectId(), fallback);
- }
- }
- }
- }
-
- /**
* Return new {@link VibrationAttributes} that only applies flags that this user has permissions
* to use.
*/
@@ -1475,30 +1392,28 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@GuardedBy("mLock")
- private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationStepConductor conductor) {
- if (conductor == null) {
+ private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationSession session) {
+ if (session == null) {
return false;
}
- HalVibration vib = conductor.getVibration();
- return mVibrationSettings.shouldCancelVibrationOnScreenOff(vib.callerInfo,
- vib.stats.getCreateUptimeMillis());
+ return mVibrationSettings.shouldCancelVibrationOnScreenOff(session.getCallerInfo(),
+ session.getCreateUptimeMillis());
}
@GuardedBy("mLock")
- private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationStepConductor conductor) {
- if (conductor == null) {
+ private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationSession session) {
+ if (session == null) {
return false;
}
- return checkAppOpModeLocked(conductor.getVibration().callerInfo)
- != AppOpsManager.MODE_ALLOWED;
+ return checkAppOpModeLocked(session.getCallerInfo()) != AppOpsManager.MODE_ALLOWED;
}
@GuardedBy("mLock")
- private boolean shouldCancelOnFgUserRequest(@Nullable VibrationStepConductor conductor) {
- if (conductor == null) {
+ private boolean shouldCancelOnFgUserRequest(@Nullable VibrationSession session) {
+ if (session == null) {
return false;
}
- return conductor.getVibration().callerInfo.attrs.getUsageClass() == USAGE_CLASS_ALARM;
+ return session.getCallerInfo().attrs.getUsageClass() == USAGE_CLASS_ALARM;
}
@GuardedBy("mLock")
@@ -1660,17 +1575,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
if (mCurrentVibration != null) {
// This is when we consider the current vibration complete, report metrics.
- reportFinishedVibrationLocked();
+ if (DEBUG) {
+ Slog.d(TAG, "Reporting vibration " + vibrationId + " finished.");
+ }
+ mCurrentVibration.unlinkToDeath();
+ finishAppOpModeLocked(mCurrentVibration.getCallerInfo());
+ logAndRecordVibration(mCurrentVibration.getDebugInfo());
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
mCurrentVibration = null;
}
if (mNextVibration != null) {
- VibrationStepConductor nextConductor = mNextVibration;
+ SingleVibrationSession nextVibration = mNextVibration;
mNextVibration = null;
- Vibration.EndInfo vibrationEndInfo = startVibrationOnThreadLocked(
- nextConductor);
- if (vibrationEndInfo != null) {
- // Failed to start the vibration, end it and report metrics right away.
- endVibrationLocked(nextConductor.getVibration(), vibrationEndInfo);
+ Status startErrorStatus = startVibrationOnThreadLocked(nextVibration);
+ if (startErrorStatus != null) {
+ endVibrationLocked(nextVibration, startErrorStatus);
}
}
}
@@ -1892,17 +1811,22 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mInfo.dump(proto, fieldId);
}
}
+ /** Clears mNextVibration if set, ending it cleanly */
+ @GuardedBy("mLock")
+ private void clearNextVibrationLocked(Status status) {
+ clearNextVibrationLocked(status, /* endedBy= */ null);
+ }
/** Clears mNextVibration if set, ending it cleanly */
@GuardedBy("mLock")
- private void clearNextVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
+ private void clearNextVibrationLocked(Status status, CallerInfo endedBy) {
if (mNextVibration != null) {
if (DEBUG) {
- Slog.d(TAG, "Dropping pending vibration " + mNextVibration.getVibration().id
- + " with end info: " + vibrationEndInfo);
+ Slog.d(TAG, "Dropping pending vibration from " + mNextVibration.getCallerInfo()
+ + " with status: " + status);
}
// Clearing next vibration before playing it, end it and report metrics right away.
- endVibrationLocked(mNextVibration.getVibration(), vibrationEndInfo);
+ endVibrationLocked(mNextVibration, status, endedBy);
mNextVibration = null;
}
}
@@ -1927,7 +1851,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
setExternalControl(false, mCurrentExternalVibration.stats);
}
// The external control was turned off, end it and report metrics right away.
- reportEndedVibrationLocked(mCurrentExternalVibration);
+ logAndRecordVibration(mCurrentExternalVibration.getDebugInfo());
mCurrentExternalVibration = null;
}
@@ -1987,16 +1911,16 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
try {
// Create Vibration.Stats as close to the received request as possible, for
// tracking.
- ExternalVibrationSession externalVibration = new ExternalVibrationSession(vib);
+ ExternalVibrationSession session = new ExternalVibrationSession(vib);
// Mute the request until we run all the checks and accept the vibration.
- externalVibration.muteScale();
+ session.muteScale();
boolean alreadyUnderExternalControl = false;
boolean waitForCompletion = false;
synchronized (mLock) {
if (!hasExternalControlCapability()) {
- endVibrationLocked(externalVibration, Status.IGNORED_UNSUPPORTED);
- return externalVibration.getScale();
+ endVibrationLocked(session, Status.IGNORED_UNSUPPORTED);
+ return session.getScale();
}
if (ActivityManager.checkComponentPermission(
@@ -2006,29 +1930,30 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+ " tried to play externally controlled vibration"
+ " without VIBRATE permission, ignoring.");
- endVibrationLocked(externalVibration, Status.IGNORED_MISSING_PERMISSION);
- return externalVibration.getScale();
+ endVibrationLocked(session, Status.IGNORED_MISSING_PERMISSION);
+ return session.getScale();
}
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(
- externalVibration.callerInfo);
+ Status ignoreStatus = shouldIgnoreVibrationLocked(session.callerInfo);
+ if (ignoreStatus != null) {
+ endVibrationLocked(session, ignoreStatus);
+ return session.getScale();
+ }
- if (vibrationEndInfo == null
- && mCurrentExternalVibration != null
+ if (mCurrentExternalVibration != null
&& mCurrentExternalVibration.isHoldingSameVibration(vib)) {
// We are already playing this external vibration, so we can return the same
// scale calculated in the previous call to this method.
return mCurrentExternalVibration.getScale();
}
- if (vibrationEndInfo == null) {
- // Check if ongoing vibration is more important than this vibration.
- vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(externalVibration);
- }
-
+ // Check if ongoing vibration is more important than this vibration.
+ Vibration.EndInfo vibrationEndInfo =
+ shouldIgnoreVibrationForOngoingLocked(session);
if (vibrationEndInfo != null) {
- endVibrationLocked(externalVibration, vibrationEndInfo);
- return externalVibration.getScale();
+ endVibrationLocked(session, vibrationEndInfo.status,
+ vibrationEndInfo.endedBy);
+ return session.getScale();
}
if (mCurrentExternalVibration == null) {
@@ -2036,15 +1961,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// vibration that may be playing and ready the vibrator for external
// control.
if (mCurrentVibration != null) {
- externalVibration.stats.reportInterruptedAnotherVibration(
+ session.stats.reportInterruptedAnotherVibration(
mCurrentVibration.getVibration().callerInfo);
- clearNextVibrationLocked(
- new Vibration.EndInfo(Status.IGNORED_FOR_EXTERNAL,
- externalVibration.callerInfo));
- mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
- externalVibration.callerInfo),
- /* immediate= */ true);
+ clearNextVibrationLocked(Status.IGNORED_FOR_EXTERNAL,
+ session.callerInfo);
+ mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED,
+ session.callerInfo, /* immediate= */ true);
waitForCompletion = true;
}
} else {
@@ -2060,10 +1982,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// as we would need to mute the old one still if it came from a different
// controller.
alreadyUnderExternalControl = true;
- externalVibration.stats.reportInterruptedAnotherVibration(
+ session.stats.reportInterruptedAnotherVibration(
mCurrentExternalVibration.getCallerInfo());
endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED,
- externalVibration.callerInfo, /* continueExternalControl= */ true);
+ session.callerInfo, /* continueExternalControl= */ true);
}
}
// Wait for lock and interact with HAL to set external control outside main lock.
@@ -2071,8 +1993,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) {
Slog.e(TAG, "Timed out waiting for vibration to cancel");
synchronized (mLock) {
- endVibrationLocked(externalVibration, Status.IGNORED_ERROR_CANCELLING);
- return externalVibration.getScale();
+ endVibrationLocked(session, Status.IGNORED_ERROR_CANCELLING);
+ return session.getScale();
}
}
}
@@ -2080,7 +2002,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (DEBUG) {
Slog.d(TAG, "Vibrator going under external control.");
}
- setExternalControl(true, externalVibration.stats);
+ setExternalControl(true, session.stats);
}
synchronized (mLock) {
if (DEBUG) {
@@ -2095,14 +2017,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mVibrationSettings.update();
}
- mCurrentExternalVibration = externalVibration;
- externalVibration.linkToDeath(this::onExternalVibrationBinderDied);
- externalVibration.scale(mVibrationScaler, attrs.getUsage());
+ mCurrentExternalVibration = session;
+ session.linkToDeath(this::onExternalVibrationBinderDied);
+ session.scale(mVibrationScaler, attrs.getUsage());
// Vibrator will start receiving data from external channels after this point.
// Report current time as the vibration start time, for debugging.
- externalVibration.stats.reportStarted();
- return externalVibration.getScale();
+ session.stats.reportStarted();
+ return session.getScale();
}
} finally {
Trace.traceEnd(TRACE_TAG_VIBRATOR);
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
index 8e32813f7ecc..242883612124 100644
--- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -156,7 +156,7 @@ public class TrustedPresentationListenerController {
Listeners mRegisteredListeners = new Listeners();
- private InputWindowHandle[] mLastWindowHandles;
+ private Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> mLastWindowHandles;
private void startHandlerThreadIfNeeded() {
synchronized (mHandlerThreadLock) {
@@ -222,10 +222,10 @@ public class TrustedPresentationListenerController {
@Override
public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
DisplayInfo[] displayInfos) {
- mHandler.post(() -> computeTpl(windowHandles));
+ mHandler.post(() -> computeTpl(new Pair<>(windowHandles, displayInfos)));
}
};
- mLastWindowHandles = mWindowInfosListener.register().first;
+ mLastWindowHandles = mWindowInfosListener.register();
}
private void unregisterWindowInfosListener() {
@@ -238,28 +238,52 @@ public class TrustedPresentationListenerController {
mLastWindowHandles = null;
}
- private void computeTpl(InputWindowHandle[] windowHandles) {
+ private void computeTpl(
+ Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> windowHandles) {
mLastWindowHandles = windowHandles;
- if (mLastWindowHandles == null || mLastWindowHandles.length == 0
+ if (mLastWindowHandles == null || mLastWindowHandles.first.length == 0
|| mRegisteredListeners.isEmpty()) {
return;
}
Rect tmpRect = new Rect();
+ RectF tmpRectF = new RectF();
+ Rect tmpLogicalDisplaySize = new Rect();
Matrix tmpInverseMatrix = new Matrix();
float[] tmpMatrix = new float[9];
Region coveredRegionsAbove = new Region();
long currTimeMs = System.currentTimeMillis();
- ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.length);
+ ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.first.length);
ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates =
new ArrayMap<>();
- for (var windowHandle : mLastWindowHandles) {
+ for (var windowHandle : mLastWindowHandles.first) {
if (!windowHandle.canOccludePresentation) {
ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
continue;
}
- tmpRect.set(windowHandle.frame);
+ var displayFound = false;
+ tmpRectF.set(windowHandle.frame);
+ for (var displayHandle : mLastWindowHandles.second) {
+ if (displayHandle.mDisplayId == windowHandle.displayId) {
+ // Transform the window frame into display logical space and then
+ // crop by the logical display size
+ displayHandle.mTransform.mapRect(tmpRectF);
+ tmpRectF.round(tmpRect);
+ tmpLogicalDisplaySize.set(0, 0, displayHandle.mLogicalSize.getWidth(),
+ displayHandle.mLogicalSize.getHeight());
+ tmpRect.intersect(tmpLogicalDisplaySize);
+ displayFound = true;
+ break;
+ }
+ }
+
+ if (!displayFound) {
+ ProtoLog.v(WM_DEBUG_TPL, "Skipping %s, no associated display %d", windowHandle.name,
+ windowHandle.displayId);
+ continue;
+ }
+
var listeners = mRegisteredListeners.get(windowHandle.getWindowToken());
if (listeners != null) {
Region region = new Region();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 4e89b85305d1..2be999fc84e0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8031,8 +8031,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
"DevicePolicyManager.wipeDataWithReason() from %s, organization-owned? %s",
adminName, calledByProfileOwnerOnOrgOwnedDevice);
- wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId,
- calledOnParentInstance, factoryReset);
+ wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId, factoryReset);
}
private String getGenericWipeReason(
@@ -8188,17 +8187,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* factory reset
*/
private void wipeDataNoLock(@Nullable ComponentName admin, int flags, String internalReason,
- String wipeReasonForUser, int userId, boolean calledOnParentInstance,
- @Nullable Boolean factoryReset) {
+ String wipeReasonForUser, int userId, @Nullable Boolean factoryReset) {
wtfIfInLock();
final String adminPackage;
if (admin != null) {
adminPackage = admin.getPackageName();
} else {
- int callerId = mInjector.binderGetCallingUid();
- String[] adminPackages = mInjector.getPackageManager().getPackagesForUid(callerId);
+ int callerUid = mInjector.binderGetCallingUid();
+ String[] adminPackages = mInjector.getPackageManager().getPackagesForUid(callerUid);
Preconditions.checkState(adminPackages.length > 0,
- "Caller %s does not have any associated packages", callerId);
+ "Caller %s does not have any associated packages", callerUid);
adminPackage = adminPackages[0];
}
mInjector.binderWithCleanCallingIdentity(() -> {
@@ -8220,32 +8218,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
throw new SecurityException("Cannot wipe data. " + restriction
+ " restriction is set for user " + userId);
}
- });
- boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
- boolean isMainUser = userId == getMainUserId();
- boolean wipeDevice;
- if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
- adminPackage,
- userId)) {
- // Legacy mode
- wipeDevice = getHeadlessDeviceOwnerModeForDeviceOwner()
- == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER ? isMainUser : isSystemUser;
- } else {
- // Explicit behaviour
- if (factoryReset) {
- EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
- /*admin=*/ null,
- /*permission=*/ new String[]{MANAGE_DEVICE_POLICY_WIPE_DATA,
- MASTER_CLEAR},
- USES_POLICY_WIPE_DATA,
- adminPackage,
- factoryReset ? UserHandle.USER_ALL :
- getAffectedUser(calledOnParentInstance));
- wipeDevice = true;
+ boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
+ boolean isMainUser = userId == getMainUserId();
+ boolean wipeDevice;
+ if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
+ adminPackage,
+ userId)) {
+ // Legacy mode
+ wipeDevice = getHeadlessDeviceOwnerModeForDeviceOwner()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER ? isMainUser : isSystemUser;
} else {
- mInjector.binderWithCleanCallingIdentity(() -> {
- Preconditions.checkCallAuthorization(!isSystemUser,
+ // Explicit behaviour
+ if (factoryReset) {
+ wipeDevice = true;
+ } else {
+ Preconditions.checkState(!isSystemUser,
"User %s is a system user and cannot be removed", userId);
boolean isLastNonHeadlessUser = getUserInfo(userId).isFull()
&& mUserManager.getAliveUsers().stream()
@@ -8253,13 +8241,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
.noneMatch(UserInfo::isFull);
Preconditions.checkState(!isLastNonHeadlessUser,
"Removing user %s would leave the device without any active users. "
- + "Consider factory resetting the device instead.",
- userId);
- });
- wipeDevice = false;
+ + "Consider factory resetting the device instead.", userId);
+ wipeDevice = false;
+ }
}
- }
- mInjector.binderWithCleanCallingIdentity(() -> {
+
if (wipeDevice) {
forceWipeDeviceNoLock(
(flags & WIPE_EXTERNAL_STORAGE) != 0,
@@ -8600,7 +8586,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/* reason= */ "reportFailedPasswordAttempt()",
getFailedPasswordAttemptWipeMessage(),
userId,
- /* calledOnParentInstance= */ parent,
// factoryReset=null to enable U- behaviour
/* factoryReset= */ null);
} catch (SecurityException e) {
diff --git a/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
index 0e815d0eab95..3e731a322d4e 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
@@ -163,6 +163,7 @@ public class LowPowerStandbyControllerTest {
addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+ when(mIPowerManagerMock.isDisplayInteractive(anyInt())).thenReturn(true);
when(mDeviceConfigWrapperMock.enableCustomPolicy()).thenReturn(true);
when(mDeviceConfigWrapperMock.enableStandbyPorts()).thenReturn(true);
@@ -899,11 +900,13 @@ public class LowPowerStandbyControllerTest {
private void setInteractive() throws Exception {
when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+ when(mIPowerManagerMock.isDisplayInteractive(anyInt())).thenReturn(true);
mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));
}
private void setNonInteractive() throws Exception {
when(mIPowerManagerMock.isInteractive()).thenReturn(false);
+ when(mIPowerManagerMock.isDisplayInteractive(anyInt())).thenReturn(false);
mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF));
}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index 54a02cf3cda3..e9e21dee16a8 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -1659,6 +1659,64 @@ public class PowerManagerServiceTest {
}
@Test
+ public void testIsWakeLockLevelSupported_returnsCorrectValue() {
+ createService();
+ startSystem();
+ PowerManagerService.BinderService service = mService.getBinderServiceInstance();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK)).isTrue();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)).isTrue();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.FULL_WAKE_LOCK)).isTrue();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.DOZE_WAKE_LOCK)).isTrue();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.DRAW_WAKE_LOCK)).isTrue();
+
+ when(mDisplayManagerInternalMock.isProximitySensorAvailable(eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(true);
+ assertThat(service.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK))
+ .isTrue();
+
+ when(mDisplayManagerInternalMock.isProximitySensorAvailable(eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(false);
+ assertThat(service.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK))
+ .isFalse();
+ }
+
+ @Test
+ public void testIsWakeLockLevelSupportedWithDisplayId_nonDefaultDisplay_returnsCorrectValue() {
+ createService();
+ startSystem();
+ int displayId = Display.DEFAULT_DISPLAY + 1;
+ PowerManagerService.BinderService service = mService.getBinderServiceInstance();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.PARTIAL_WAKE_LOCK, displayId))
+ .isTrue();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK, displayId))
+ .isTrue();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.FULL_WAKE_LOCK, displayId))
+ .isTrue();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.DOZE_WAKE_LOCK, displayId))
+ .isTrue();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.DRAW_WAKE_LOCK, displayId))
+ .isTrue();
+
+ when(mDisplayManagerInternalMock.isProximitySensorAvailable(eq(displayId)))
+ .thenReturn(true);
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, displayId))
+ .isTrue();
+
+ when(mDisplayManagerInternalMock.isProximitySensorAvailable(eq(displayId)))
+ .thenReturn(false);
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, displayId))
+ .isFalse();
+ }
+
+
+ @Test
public void testWakeLock_affectsProperDisplayGroup() {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
@@ -1699,6 +1757,47 @@ public class PowerManagerServiceTest {
}
@Test
+ public void testWakeLock_nonDefaultDisplay_affectsProperDisplayGroup() {
+ final int nonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
+ final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+ new AtomicReference<>();
+ doAnswer((Answer<Void>) invocation -> {
+ listener.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = nonDefaultDisplayGroupId;
+ when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplayId)).thenReturn(info);
+
+ final String pkg = mContextSpy.getOpPackageName();
+ final Binder token = new Binder();
+ final String tag = "testWakeLock_nonDefaultDisplay_affectsProperDisplayGroup";
+
+ setMinimumScreenOffTimeoutConfig(5);
+ createService();
+ startSystem();
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+ mService.getBinderServiceInstance().acquireWakeLock(token,
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
+ null /* workSource */, null /* historyTag */, nonDefaultDisplayId, null);
+
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+
+ advanceTime(15000);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_ASLEEP);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+ }
+
+ @Test
public void testInvalidDisplayGroupWakeLock_affectsAllDisplayGroups() {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
@@ -2590,14 +2689,15 @@ public class PowerManagerServiceTest {
when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
createService();
startSystem();
- listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
verify(mInvalidateInteractiveCachesMock).call();
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+ verify(mInvalidateInteractiveCachesMock, times(2)).call();
+
mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP,
mClock.now(), 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
- verify(mInvalidateInteractiveCachesMock, times(2)).call();
+ verify(mInvalidateInteractiveCachesMock, times(3)).call();
}
@Test
@@ -2616,14 +2716,15 @@ public class PowerManagerServiceTest {
when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
createService();
startSystem();
- listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
verify(mInvalidateInteractiveCachesMock).call();
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+ verify(mInvalidateInteractiveCachesMock, times(2)).call();
+
mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, mClock.now(),
0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
- verify(mInvalidateInteractiveCachesMock, times(2)).call();
+ verify(mInvalidateInteractiveCachesMock, times(3)).call();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
index 50f3a88cc3a2..5bcddc41eb87 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
@@ -1,6 +1,10 @@
package com.android.server.locksettings;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
import android.platform.test.annotations.Presubmit;
@@ -56,4 +60,44 @@ public class WeaverBasedSyntheticPasswordTests extends SyntheticPasswordTests {
mService.initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
assertEquals(Sets.newHashSet(0), mPasswordSlotManager.getUsedSlots());
}
+
+ private int getNumUsedWeaverSlots() {
+ return mPasswordSlotManager.getUsedSlots().size();
+ }
+
+ @Test
+ public void testDisableWeaverOnUnsecuredUsers_false() {
+ final int userId = PRIMARY_USER_ID;
+ when(mResources.getBoolean(eq(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers)))
+ .thenReturn(false);
+ assertEquals(0, getNumUsedWeaverSlots());
+ mService.initializeSyntheticPassword(userId);
+ assertEquals(1, getNumUsedWeaverSlots());
+ assertTrue(mService.setLockCredential(newPassword("password"), nonePassword(), userId));
+ assertEquals(1, getNumUsedWeaverSlots());
+ assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"), userId));
+ assertEquals(1, getNumUsedWeaverSlots());
+ }
+
+ @Test
+ public void testDisableWeaverOnUnsecuredUsers_true() {
+ final int userId = PRIMARY_USER_ID;
+ when(mResources.getBoolean(eq(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers)))
+ .thenReturn(true);
+ assertEquals(0, getNumUsedWeaverSlots());
+ mService.initializeSyntheticPassword(userId);
+ assertEquals(0, getNumUsedWeaverSlots());
+ assertTrue(mService.setLockCredential(newPassword("password"), nonePassword(), userId));
+ assertEquals(1, getNumUsedWeaverSlots());
+ assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"), userId));
+ assertEquals(0, getNumUsedWeaverSlots());
+ }
+
+ @Test
+ public void testDisableWeaverOnUnsecuredUsers_defaultsToFalse() {
+ assertFalse(mResources.getBoolean(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers));
+ }
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index e83a4b22d9bf..7536f5f54ba2 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -47,7 +47,6 @@ import android.hardware.vibrator.IVibrator;
import android.hardware.vibrator.IVibratorManager;
import android.os.CombinedVibration;
import android.os.Handler;
-import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.Process;
@@ -119,7 +118,6 @@ public class VibrationThreadTest {
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@Mock private VibrationThread.VibratorManagerHooks mManagerHooks;
@Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
- @Mock private IBinder mVibrationToken;
@Mock private VibrationConfig mVibrationConfigMock;
@Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
@@ -668,7 +666,7 @@ public class VibrationThreadTest {
VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
HalVibration vibration = createVibration(CombinedVibration.createParallel(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
- vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback);
+ vibration.fillFallbacks(unused -> fallback);
startThreadAndDispatcher(vibration);
waitForCompletion();
@@ -848,7 +846,7 @@ public class VibrationThreadTest {
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.compose();
HalVibration vibration = createVibration(CombinedVibration.createParallel(effect));
- vibration.addFallback(VibrationEffect.EFFECT_TICK, fallback);
+ vibration.fillFallbacks(unused -> fallback);
startThreadAndDispatcher(vibration);
waitForCompletion();
@@ -954,7 +952,8 @@ public class VibrationThreadTest {
assertTrue(mThread.isRunningVibrationId(vibration.id));
assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
- mVibrationConductor.binderDied();
+ mVibrationConductor.notifyCancelled(
+ new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), /* immediate= */ false);
waitForCompletion();
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -1575,7 +1574,8 @@ public class VibrationThreadTest {
TEST_TIMEOUT_MILLIS));
assertTrue(mThread.isRunningVibrationId(vibration.id));
- mVibrationConductor.binderDied();
+ mVibrationConductor.notifyCancelled(
+ new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), /* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibration, Status.CANCELLED_BINDER_DIED);
@@ -1865,9 +1865,9 @@ public class VibrationThreadTest {
VibrationAttributes attrs = new VibrationAttributes.Builder()
.setUsage(usage)
.build();
- HalVibration vib = new HalVibration(mVibrationToken,
- CombinedVibration.createParallel(effect),
- new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+ HalVibration vib = new HalVibration(
+ new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"),
+ CombinedVibration.createParallel(effect));
return startThreadAndDispatcher(vib, requestVibrationParamsFuture);
}
@@ -1903,8 +1903,8 @@ public class VibrationThreadTest {
}
private HalVibration createVibration(CombinedVibration effect) {
- return new HalVibration(mVibrationToken, effect,
- new CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+ return new HalVibration(new CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"),
+ effect);
}
private SparseArray<VibratorController> createVibratorControllers() {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
index cd057b619000..1d1b4e271e19 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
@@ -115,6 +115,6 @@ public class VibratorFrameworkStatsLoggerTest {
}
private static VibrationStats.StatsInfo newEmptyStatsInfo() {
- return new VibrationStats.StatsInfo(0, 0, 0, Status.FINISHED, new VibrationStats(), 0L);
+ return new VibrationStats.StatsInfo(0, 0, 0, Status.FINISHED, new VibrationStats());
}
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 538c3fc2ddae..b7821623855c 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1791,28 +1791,6 @@ public class VibratorManagerServiceTest {
}
@Test
- public void performHapticFeedback_usesServiceAsToken() throws Exception {
- VibratorManagerService service = createSystemReadyService();
-
- HalVibration vibration =
- performHapticFeedbackAndWaitUntilFinished(
- service, HapticFeedbackConstants.SCROLL_TICK, /* always= */ true);
-
- assertTrue(vibration.callerToken == service);
- }
-
- @Test
- public void performHapticFeedbackForInputDevice_usesServiceAsToken() throws Exception {
- VibratorManagerService service = createSystemReadyService();
-
- HalVibration vibration = performHapticFeedbackForInputDeviceAndWaitUntilFinished(
- service, HapticFeedbackConstants.SCROLL_TICK, /* inputDeviceId= */ 0,
- InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
-
- assertTrue(vibration.callerToken == service);
- }
-
- @Test
@RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_vendorEffectsWithoutPermission_doesNotVibrate() throws Exception {
// Deny permission to vibrate with vendor effects
@@ -2147,6 +2125,27 @@ public class VibratorManagerServiceTest {
}
@Test
+ public void cancelVibrate_externalVibration_cancelWithDifferentToken() {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ createSystemReadyService();
+
+ IBinder vibrationBinderToken = mock(IBinder.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS,
+ mock(IExternalVibrationController.class), vibrationBinderToken);
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+
+ IBinder cancelBinderToken = mock(IBinder.class);
+ mService.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, cancelBinderToken);
+
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+ assertEquals(Arrays.asList(false, true, false),
+ mVibratorProviders.get(1).getExternalControlStates());
+ }
+
+ @Test
public void onExternalVibration_ignoreVibrationFromVirtualDevices() {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index ff9cba2795fd..f001232ea93e 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -33,6 +33,7 @@ import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.TelephonyUtils;
import java.util.ArrayList;
@@ -185,11 +186,7 @@ public final class CarrierAppUtils {
if (hasPrivileges) {
// Only update enabled state for the app on /system. Once it has been
// updated we shouldn't touch it.
- if (!isUpdatedSystemApp(ai) && enabledSetting
- == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
- || enabledSetting
- == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
- || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
+ if (shouldUpdateEnabledState(ai, enabledSetting)) {
Log.i(TAG, "Update state (" + packageName + "): ENABLED for user "
+ userId);
context.createContextAsUser(UserHandle.of(userId), 0)
@@ -330,6 +327,21 @@ public final class CarrierAppUtils {
}
}
+ private static boolean shouldUpdateEnabledState(ApplicationInfo appInfo, int enabledSetting) {
+ if (Flags.cleanupCarrierAppUpdateEnabledStateLogic()) {
+ return !isUpdatedSystemApp(appInfo)
+ && (enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+ || enabledSetting
+ == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+ || (appInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0);
+ } else {
+ return !isUpdatedSystemApp(appInfo)
+ && enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+ || enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+ || (appInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0;
+ }
+ }
+
/**
* Returns the list of "default" carrier apps.
*
diff --git a/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java
index e3a129fb1059..d03ad5cb2877 100644
--- a/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java
+++ b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java
@@ -61,8 +61,13 @@ public class ApplicationSharedMemoryTest {
@Test
public void canRead() {
ApplicationSharedMemory instance = ApplicationSharedMemory.getInstance();
- instance.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis();
- // Don't actually care about the value of the above.
+ try {
+ instance.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis();
+ // Don't actually care about the value of the above.
+ } catch (java.time.DateTimeException e) {
+ // This exception is okay during testing. It means there was no time source, which
+ // could be because of network problems or a feature being flagged off.
+ }
}
/** Application processes should not have mutable access. */
diff --git a/tests/Internal/src/com/android/internal/os/OWNERS b/tests/Internal/src/com/android/internal/os/OWNERS
new file mode 100644
index 000000000000..64ffa463bac1
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/os/OWNERS
@@ -0,0 +1,2 @@
+# ApplicationSharedMemory
+per-file *ApplicationSharedMemory* = file:/PERFORMANCE_OWNERS